From dd93f9a2c4f6a3476f178e9da83acb088ff477a5 Mon Sep 17 00:00:00 2001 From: Anthony Rubick <68485672+AnthonyMichaelTDM@users.noreply.github.com> Date: Thu, 17 Jul 2025 19:50:57 -0700 Subject: [PATCH] feat(plugins): Implement plugin API key management and authentication middleware The purpose of this is to allow plugins to access certain internal APIs via - Added PluginAPIKey and APIKeyManager for managing API keys associated with plugins. - Introduced PluginAuthMiddleware to handle API key validation for plugin requests. - Updated RouterDef to support plugin accessible endpoints with authentication. - Modified various API registration functions to include plugin accessibility checks. - Enhanced plugin lifecycle management to generate and revoke API keys as needed. - Updated plugin specifications to include permitted API endpoints for access control. --- src/api.go | 326 +++++++++--------- src/def.go | 3 + src/mod/auth/auth.go | 8 +- src/mod/auth/plugin_apikey_manager.go | 152 ++++++++ src/mod/auth/plugin_middleware.go | 77 +++++ src/mod/auth/router.go | 43 ++- src/mod/plugins/lifecycle.go | 19 + src/mod/plugins/typdef.go | 5 + .../plugins/zoraxy_plugin/zoraxy_plugin.go | 17 +- src/start.go | 11 +- 10 files changed, 470 insertions(+), 191 deletions(-) create mode 100644 src/mod/auth/plugin_apikey_manager.go create mode 100644 src/mod/auth/plugin_middleware.go diff --git a/src/api.go b/src/api.go index 17fd3c4..b93c351 100644 --- a/src/api.go +++ b/src/api.go @@ -25,233 +25,233 @@ import ( // Register the APIs for HTTP proxy management functions func RegisterHTTPProxyAPIs(authRouter *auth.RouterDef) { /* Reverse Proxy Settings & Status */ - authRouter.HandleFunc("/api/proxy/enable", ReverseProxyHandleOnOff) - authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint) - authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus) - authRouter.HandleFunc("/api/proxy/toggle", ReverseProxyToggleRuleSet) - authRouter.HandleFunc("/api/proxy/list", ReverseProxyList) - authRouter.HandleFunc("/api/proxy/listTags", ReverseProxyListTags) - authRouter.HandleFunc("/api/proxy/detail", ReverseProxyListDetail) - authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint) - authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias) - authRouter.HandleFunc("/api/proxy/setTlsConfig", ReverseProxyHandleSetTlsConfig) - authRouter.HandleFunc("/api/proxy/setHostname", ReverseProxyHandleSetHostname) - authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint) - authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials) - authRouter.HandleFunc("/api/proxy/tlscheck", domainsniff.HandleCheckSiteSupportTLS) - authRouter.HandleFunc("/api/proxy/setIncoming", HandleIncomingPortSet) - authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect) - authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener) - authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck) - authRouter.HandleFunc("/api/proxy/developmentMode", HandleDevelopmentModeChange) + authRouter.HandleFunc("/api/proxy/enable", ReverseProxyHandleOnOff, false) + authRouter.HandleFunc("/api/proxy/add", ReverseProxyHandleAddEndpoint, false) + authRouter.HandleFunc("/api/proxy/status", ReverseProxyStatus, true) + authRouter.HandleFunc("/api/proxy/toggle", ReverseProxyToggleRuleSet, false) + authRouter.HandleFunc("/api/proxy/list", ReverseProxyList, true) + authRouter.HandleFunc("/api/proxy/listTags", ReverseProxyListTags, true) + authRouter.HandleFunc("/api/proxy/detail", ReverseProxyListDetail, true) + authRouter.HandleFunc("/api/proxy/edit", ReverseProxyHandleEditEndpoint, false) + authRouter.HandleFunc("/api/proxy/setAlias", ReverseProxyHandleAlias, false) + authRouter.HandleFunc("/api/proxy/setTlsConfig", ReverseProxyHandleSetTlsConfig, false) + authRouter.HandleFunc("/api/proxy/setHostname", ReverseProxyHandleSetHostname, false) + authRouter.HandleFunc("/api/proxy/del", DeleteProxyEndpoint, false) + authRouter.HandleFunc("/api/proxy/updateCredentials", UpdateProxyBasicAuthCredentials, false) + authRouter.HandleFunc("/api/proxy/tlscheck", domainsniff.HandleCheckSiteSupportTLS, false) + authRouter.HandleFunc("/api/proxy/setIncoming", HandleIncomingPortSet, false) + authRouter.HandleFunc("/api/proxy/useHttpsRedirect", HandleUpdateHttpsRedirect, false) + authRouter.HandleFunc("/api/proxy/listenPort80", HandleUpdatePort80Listener, false) + authRouter.HandleFunc("/api/proxy/requestIsProxied", HandleManagementProxyCheck, false) + authRouter.HandleFunc("/api/proxy/developmentMode", HandleDevelopmentModeChange, false) /* Reverse proxy upstream (load balance) */ - authRouter.HandleFunc("/api/proxy/upstream/list", ReverseProxyUpstreamList) - authRouter.HandleFunc("/api/proxy/upstream/add", ReverseProxyUpstreamAdd) - authRouter.HandleFunc("/api/proxy/upstream/setPriority", ReverseProxyUpstreamSetPriority) - authRouter.HandleFunc("/api/proxy/upstream/update", ReverseProxyUpstreamUpdate) - authRouter.HandleFunc("/api/proxy/upstream/remove", ReverseProxyUpstreamDelete) + authRouter.HandleFunc("/api/proxy/upstream/list", ReverseProxyUpstreamList, false) + authRouter.HandleFunc("/api/proxy/upstream/add", ReverseProxyUpstreamAdd, false) + authRouter.HandleFunc("/api/proxy/upstream/setPriority", ReverseProxyUpstreamSetPriority, false) + authRouter.HandleFunc("/api/proxy/upstream/update", ReverseProxyUpstreamUpdate, false) + authRouter.HandleFunc("/api/proxy/upstream/remove", ReverseProxyUpstreamDelete, false) /* Reverse proxy virtual directory */ - authRouter.HandleFunc("/api/proxy/vdir/list", ReverseProxyListVdir) - authRouter.HandleFunc("/api/proxy/vdir/add", ReverseProxyAddVdir) - authRouter.HandleFunc("/api/proxy/vdir/del", ReverseProxyDeleteVdir) - authRouter.HandleFunc("/api/proxy/vdir/edit", ReverseProxyEditVdir) + authRouter.HandleFunc("/api/proxy/vdir/list", ReverseProxyListVdir, false) + authRouter.HandleFunc("/api/proxy/vdir/add", ReverseProxyAddVdir, false) + authRouter.HandleFunc("/api/proxy/vdir/del", ReverseProxyDeleteVdir, false) + authRouter.HandleFunc("/api/proxy/vdir/edit", ReverseProxyEditVdir, false) /* Reverse proxy user-defined header */ - authRouter.HandleFunc("/api/proxy/header/list", HandleCustomHeaderList) - authRouter.HandleFunc("/api/proxy/header/add", HandleCustomHeaderAdd) - authRouter.HandleFunc("/api/proxy/header/remove", HandleCustomHeaderRemove) - authRouter.HandleFunc("/api/proxy/header/handleHSTS", HandleHSTSState) - authRouter.HandleFunc("/api/proxy/header/handleHopByHop", HandleHopByHop) - authRouter.HandleFunc("/api/proxy/header/handleHostOverwrite", HandleHostOverwrite) - authRouter.HandleFunc("/api/proxy/header/handlePermissionPolicy", HandlePermissionPolicy) - authRouter.HandleFunc("/api/proxy/header/handleWsHeaderBehavior", HandleWsHeaderBehavior) + authRouter.HandleFunc("/api/proxy/header/list", HandleCustomHeaderList, true) + authRouter.HandleFunc("/api/proxy/header/add", HandleCustomHeaderAdd, false) + authRouter.HandleFunc("/api/proxy/header/remove", HandleCustomHeaderRemove, false) + authRouter.HandleFunc("/api/proxy/header/handleHSTS", HandleHSTSState, false) + authRouter.HandleFunc("/api/proxy/header/handleHopByHop", HandleHopByHop, false) + authRouter.HandleFunc("/api/proxy/header/handleHostOverwrite", HandleHostOverwrite, false) + authRouter.HandleFunc("/api/proxy/header/handlePermissionPolicy", HandlePermissionPolicy, false) + authRouter.HandleFunc("/api/proxy/header/handleWsHeaderBehavior", HandleWsHeaderBehavior, false) /* Reverse proxy auth related */ - authRouter.HandleFunc("/api/proxy/auth/exceptions/list", ListProxyBasicAuthExceptionPaths) - authRouter.HandleFunc("/api/proxy/auth/exceptions/add", AddProxyBasicAuthExceptionPaths) - authRouter.HandleFunc("/api/proxy/auth/exceptions/delete", RemoveProxyBasicAuthExceptionPaths) + authRouter.HandleFunc("/api/proxy/auth/exceptions/list", ListProxyBasicAuthExceptionPaths, true) + authRouter.HandleFunc("/api/proxy/auth/exceptions/add", AddProxyBasicAuthExceptionPaths, false) + authRouter.HandleFunc("/api/proxy/auth/exceptions/delete", RemoveProxyBasicAuthExceptionPaths, false) } // Register the APIs for TLS / SSL certificate management functions func RegisterTLSAPIs(authRouter *auth.RouterDef) { //Global certificate settings - authRouter.HandleFunc("/api/cert/tls", handleToggleTLSProxy) - authRouter.HandleFunc("/api/cert/tlsRequireLatest", handleSetTlsRequireLatest) - authRouter.HandleFunc("/api/cert/resolve", handleCertTryResolve) - authRouter.HandleFunc("/api/cert/setPreferredCertificate", handleSetDomainPreferredCertificate) + authRouter.HandleFunc("/api/cert/tls", handleToggleTLSProxy, false) + authRouter.HandleFunc("/api/cert/tlsRequireLatest", handleSetTlsRequireLatest, false) + authRouter.HandleFunc("/api/cert/resolve", handleCertTryResolve, false) + authRouter.HandleFunc("/api/cert/setPreferredCertificate", handleSetDomainPreferredCertificate, false) //Certificate store functions - authRouter.HandleFunc("/api/cert/upload", tlsCertManager.HandleCertUpload) - authRouter.HandleFunc("/api/cert/download", tlsCertManager.HandleCertDownload) - authRouter.HandleFunc("/api/cert/list", tlsCertManager.HandleListCertificate) - authRouter.HandleFunc("/api/cert/listdomains", tlsCertManager.HandleListDomains) - authRouter.HandleFunc("/api/cert/checkDefault", tlsCertManager.HandleDefaultCertCheck) - authRouter.HandleFunc("/api/cert/delete", tlsCertManager.HandleCertRemove) - authRouter.HandleFunc("/api/cert/selfsign", tlsCertManager.HandleSelfSignCertGenerate) + authRouter.HandleFunc("/api/cert/upload", tlsCertManager.HandleCertUpload, false) + authRouter.HandleFunc("/api/cert/download", tlsCertManager.HandleCertDownload, false) + authRouter.HandleFunc("/api/cert/list", tlsCertManager.HandleListCertificate, false) + authRouter.HandleFunc("/api/cert/listdomains", tlsCertManager.HandleListDomains, false) + authRouter.HandleFunc("/api/cert/checkDefault", tlsCertManager.HandleDefaultCertCheck, false) + authRouter.HandleFunc("/api/cert/delete", tlsCertManager.HandleCertRemove, false) + authRouter.HandleFunc("/api/cert/selfsign", tlsCertManager.HandleSelfSignCertGenerate, false) } // Register the APIs for Authentication handlers like Forward Auth and OAUTH2 func RegisterAuthenticationHandlerAPIs(authRouter *auth.RouterDef) { - authRouter.HandleFunc("/api/sso/forward-auth", forwardAuthRouter.HandleAPIOptions) - authRouter.HandleFunc("/api/sso/OAuth2", oauth2Router.HandleSetOAuth2Settings) + authRouter.HandleFunc("/api/sso/forward-auth", forwardAuthRouter.HandleAPIOptions, false) + authRouter.HandleFunc("/api/sso/OAuth2", oauth2Router.HandleSetOAuth2Settings, false) } // Register the APIs for redirection rules management functions func RegisterRedirectionAPIs(authRouter *auth.RouterDef) { - authRouter.HandleFunc("/api/redirect/list", handleListRedirectionRules) - authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule) - authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule) - authRouter.HandleFunc("/api/redirect/edit", handleEditRedirectionRule) - authRouter.HandleFunc("/api/redirect/regex", handleToggleRedirectRegexpSupport) + authRouter.HandleFunc("/api/redirect/list", handleListRedirectionRules, true) + authRouter.HandleFunc("/api/redirect/add", handleAddRedirectionRule, false) + authRouter.HandleFunc("/api/redirect/delete", handleDeleteRedirectionRule, false) + authRouter.HandleFunc("/api/redirect/edit", handleEditRedirectionRule, false) + authRouter.HandleFunc("/api/redirect/regex", handleToggleRedirectRegexpSupport, false) } // Register the APIs for access rules management functions func RegisterAccessRuleAPIs(authRouter *auth.RouterDef) { /* Access Rules Settings & Status */ - authRouter.HandleFunc("/api/access/list", handleListAccessRules) - authRouter.HandleFunc("/api/access/attach", handleAttachRuleToHost) - authRouter.HandleFunc("/api/access/create", handleCreateAccessRule) - authRouter.HandleFunc("/api/access/remove", handleRemoveAccessRule) - authRouter.HandleFunc("/api/access/update", handleUpadateAccessRule) + authRouter.HandleFunc("/api/access/list", handleListAccessRules, true) + authRouter.HandleFunc("/api/access/attach", handleAttachRuleToHost, false) + authRouter.HandleFunc("/api/access/create", handleCreateAccessRule, false) + authRouter.HandleFunc("/api/access/remove", handleRemoveAccessRule, false) + authRouter.HandleFunc("/api/access/update", handleUpadateAccessRule, false) /* Blacklist */ - authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted) - authRouter.HandleFunc("/api/blacklist/country/add", handleCountryBlacklistAdd) - authRouter.HandleFunc("/api/blacklist/country/remove", handleCountryBlacklistRemove) - authRouter.HandleFunc("/api/blacklist/ip/add", handleIpBlacklistAdd) - authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove) - authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable) + authRouter.HandleFunc("/api/blacklist/list", handleListBlacklisted, true) + authRouter.HandleFunc("/api/blacklist/country/add", handleCountryBlacklistAdd, false) + authRouter.HandleFunc("/api/blacklist/country/remove", handleCountryBlacklistRemove, false) + authRouter.HandleFunc("/api/blacklist/ip/add", handleIpBlacklistAdd, true) + authRouter.HandleFunc("/api/blacklist/ip/remove", handleIpBlacklistRemove, true) + authRouter.HandleFunc("/api/blacklist/enable", handleBlacklistEnable, true) /* Whitelist */ - authRouter.HandleFunc("/api/whitelist/list", handleListWhitelisted) - authRouter.HandleFunc("/api/whitelist/country/add", handleCountryWhitelistAdd) - authRouter.HandleFunc("/api/whitelist/country/remove", handleCountryWhitelistRemove) - authRouter.HandleFunc("/api/whitelist/ip/add", handleIpWhitelistAdd) - authRouter.HandleFunc("/api/whitelist/ip/remove", handleIpWhitelistRemove) - authRouter.HandleFunc("/api/whitelist/enable", handleWhitelistEnable) - authRouter.HandleFunc("/api/whitelist/allowLocal", handleWhitelistAllowLoopback) + authRouter.HandleFunc("/api/whitelist/list", handleListWhitelisted, true) + authRouter.HandleFunc("/api/whitelist/country/add", handleCountryWhitelistAdd, false) + authRouter.HandleFunc("/api/whitelist/country/remove", handleCountryWhitelistRemove, false) + authRouter.HandleFunc("/api/whitelist/ip/add", handleIpWhitelistAdd, false) + authRouter.HandleFunc("/api/whitelist/ip/remove", handleIpWhitelistRemove, false) + authRouter.HandleFunc("/api/whitelist/enable", handleWhitelistEnable, false) + authRouter.HandleFunc("/api/whitelist/allowLocal", handleWhitelistAllowLoopback, false) /* Quick Ban List */ - authRouter.HandleFunc("/api/quickban/list", handleListQuickBan) + authRouter.HandleFunc("/api/quickban/list", handleListQuickBan, false) } // Register the APIs for path blocking rules management functions, WIP func RegisterPathRuleAPIs(authRouter *auth.RouterDef) { - authRouter.HandleFunc("/api/pathrule/add", pathRuleHandler.HandleAddBlockingPath) - authRouter.HandleFunc("/api/pathrule/list", pathRuleHandler.HandleListBlockingPath) - authRouter.HandleFunc("/api/pathrule/remove", pathRuleHandler.HandleRemoveBlockingPath) + authRouter.HandleFunc("/api/pathrule/add", pathRuleHandler.HandleAddBlockingPath, false) + authRouter.HandleFunc("/api/pathrule/list", pathRuleHandler.HandleListBlockingPath, false) + authRouter.HandleFunc("/api/pathrule/remove", pathRuleHandler.HandleRemoveBlockingPath, false) } // Register the APIs statistic anlysis and uptime monitoring functions func RegisterStatisticalAPIs(authRouter *auth.RouterDef) { /* Traffic Summary */ - authRouter.HandleFunc("/api/stats/summary", statisticCollector.HandleTodayStatLoad) - authRouter.HandleFunc("/api/stats/countries", HandleCountryDistrSummary) - authRouter.HandleFunc("/api/stats/netstat", netstatBuffers.HandleGetNetworkInterfaceStats) - authRouter.HandleFunc("/api/stats/netstatgraph", netstatBuffers.HandleGetBufferedNetworkInterfaceStats) - authRouter.HandleFunc("/api/stats/listnic", netstat.HandleListNetworkInterfaces) + authRouter.HandleFunc("/api/stats/summary", statisticCollector.HandleTodayStatLoad, false) + authRouter.HandleFunc("/api/stats/countries", HandleCountryDistrSummary, false) + authRouter.HandleFunc("/api/stats/netstat", netstatBuffers.HandleGetNetworkInterfaceStats, false) + authRouter.HandleFunc("/api/stats/netstatgraph", netstatBuffers.HandleGetBufferedNetworkInterfaceStats, false) + authRouter.HandleFunc("/api/stats/listnic", netstat.HandleListNetworkInterfaces, false) /* Zoraxy Analytic */ - authRouter.HandleFunc("/api/analytic/list", AnalyticLoader.HandleSummaryList) - authRouter.HandleFunc("/api/analytic/load", AnalyticLoader.HandleLoadTargetDaySummary) - authRouter.HandleFunc("/api/analytic/loadRange", AnalyticLoader.HandleLoadTargetRangeSummary) - authRouter.HandleFunc("/api/analytic/exportRange", AnalyticLoader.HandleRangeExport) - authRouter.HandleFunc("/api/analytic/resetRange", AnalyticLoader.HandleRangeReset) + authRouter.HandleFunc("/api/analytic/list", AnalyticLoader.HandleSummaryList, false) + authRouter.HandleFunc("/api/analytic/load", AnalyticLoader.HandleLoadTargetDaySummary, false) + authRouter.HandleFunc("/api/analytic/loadRange", AnalyticLoader.HandleLoadTargetRangeSummary, false) + authRouter.HandleFunc("/api/analytic/exportRange", AnalyticLoader.HandleRangeExport, false) + authRouter.HandleFunc("/api/analytic/resetRange", AnalyticLoader.HandleRangeReset, false) /* UpTime Monitor */ - authRouter.HandleFunc("/api/utm/list", HandleUptimeMonitorListing) + authRouter.HandleFunc("/api/utm/list", HandleUptimeMonitorListing, false) } // Register the APIs for Stream (TCP / UDP) Proxy management functions func RegisterStreamProxyAPIs(authRouter *auth.RouterDef) { - authRouter.HandleFunc("/api/streamprox/config/add", streamProxyManager.HandleAddProxyConfig) - authRouter.HandleFunc("/api/streamprox/config/edit", streamProxyManager.HandleEditProxyConfigs) - authRouter.HandleFunc("/api/streamprox/config/list", streamProxyManager.HandleListConfigs) - authRouter.HandleFunc("/api/streamprox/config/start", streamProxyManager.HandleStartProxy) - authRouter.HandleFunc("/api/streamprox/config/stop", streamProxyManager.HandleStopProxy) - authRouter.HandleFunc("/api/streamprox/config/delete", streamProxyManager.HandleRemoveProxy) - authRouter.HandleFunc("/api/streamprox/config/status", streamProxyManager.HandleGetProxyStatus) + authRouter.HandleFunc("/api/streamprox/config/add", streamProxyManager.HandleAddProxyConfig, false) + authRouter.HandleFunc("/api/streamprox/config/edit", streamProxyManager.HandleEditProxyConfigs, false) + authRouter.HandleFunc("/api/streamprox/config/list", streamProxyManager.HandleListConfigs, false) + authRouter.HandleFunc("/api/streamprox/config/start", streamProxyManager.HandleStartProxy, false) + authRouter.HandleFunc("/api/streamprox/config/stop", streamProxyManager.HandleStopProxy, false) + authRouter.HandleFunc("/api/streamprox/config/delete", streamProxyManager.HandleRemoveProxy, false) + authRouter.HandleFunc("/api/streamprox/config/status", streamProxyManager.HandleGetProxyStatus, false) } // Register the APIs for mDNS service management functions func RegisterMDNSAPIs(authRouter *auth.RouterDef) { - authRouter.HandleFunc("/api/mdns/list", HandleMdnsListing) - authRouter.HandleFunc("/api/mdns/discover", HandleMdnsScanning) + authRouter.HandleFunc("/api/mdns/list", HandleMdnsListing, false) + authRouter.HandleFunc("/api/mdns/discover", HandleMdnsScanning, false) } // Register the APIs for ACME and Auto Renewer management functions func RegisterACMEAndAutoRenewerAPIs(authRouter *auth.RouterDef) { /* ACME Core */ - authRouter.HandleFunc("/api/acme/listExpiredDomains", acmeHandler.HandleGetExpiredDomains) - authRouter.HandleFunc("/api/acme/obtainCert", AcmeCheckAndHandleRenewCertificate) + authRouter.HandleFunc("/api/acme/listExpiredDomains", acmeHandler.HandleGetExpiredDomains, false) + authRouter.HandleFunc("/api/acme/obtainCert", AcmeCheckAndHandleRenewCertificate, false) /* Auto Renewer */ - authRouter.HandleFunc("/api/acme/autoRenew/enable", acmeAutoRenewer.HandleAutoRenewEnable) - authRouter.HandleFunc("/api/acme/autoRenew/ca", HandleACMEPreferredCA) - authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail) - authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains) - authRouter.HandleFunc("/api/acme/autoRenew/setEAB", acmeAutoRenewer.HanldeSetEAB) - authRouter.HandleFunc("/api/acme/autoRenew/setDNS", acmeAutoRenewer.HandleSetDNS) - authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains) - authRouter.HandleFunc("/api/acme/autoRenew/renewPolicy", acmeAutoRenewer.HandleRenewPolicy) - authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow) - authRouter.HandleFunc("/api/acme/dns/providers", acmedns.HandleServeProvidersJson) + authRouter.HandleFunc("/api/acme/autoRenew/enable", acmeAutoRenewer.HandleAutoRenewEnable, false) + authRouter.HandleFunc("/api/acme/autoRenew/ca", HandleACMEPreferredCA, false) + authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail, false) + authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains, false) + authRouter.HandleFunc("/api/acme/autoRenew/setEAB", acmeAutoRenewer.HanldeSetEAB, false) + authRouter.HandleFunc("/api/acme/autoRenew/setDNS", acmeAutoRenewer.HandleSetDNS, false) + authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains, false) + authRouter.HandleFunc("/api/acme/autoRenew/renewPolicy", acmeAutoRenewer.HandleRenewPolicy, false) + authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow, false) + authRouter.HandleFunc("/api/acme/dns/providers", acmedns.HandleServeProvidersJson, false) /* ACME Wizard */ - authRouter.HandleFunc("/api/acme/wizard", acmewizard.HandleGuidedStepCheck) + authRouter.HandleFunc("/api/acme/wizard", acmewizard.HandleGuidedStepCheck, false) } // Register the APIs for Static Web Server management functions func RegisterStaticWebServerAPIs(authRouter *auth.RouterDef) { /* Static Web Server Controls */ - authRouter.HandleFunc("/api/webserv/status", staticWebServer.HandleGetStatus) - authRouter.HandleFunc("/api/webserv/start", staticWebServer.HandleStartServer) - authRouter.HandleFunc("/api/webserv/stop", staticWebServer.HandleStopServer) - authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange) - authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing) - authRouter.HandleFunc("/api/webserv/disableListenAllInterface", staticWebServer.SetDisableListenToAllInterface) + authRouter.HandleFunc("/api/webserv/status", staticWebServer.HandleGetStatus, false) + authRouter.HandleFunc("/api/webserv/start", staticWebServer.HandleStartServer, false) + authRouter.HandleFunc("/api/webserv/stop", staticWebServer.HandleStopServer, false) + authRouter.HandleFunc("/api/webserv/setPort", HandleStaticWebServerPortChange, false) + authRouter.HandleFunc("/api/webserv/setDirList", staticWebServer.SetEnableDirectoryListing, false) + authRouter.HandleFunc("/api/webserv/disableListenAllInterface", staticWebServer.SetDisableListenToAllInterface, false) /* File Manager */ if *allowWebFileManager { - authRouter.HandleFunc("/api/fs/list", staticWebServer.FileManager.HandleList) - authRouter.HandleFunc("/api/fs/upload", staticWebServer.FileManager.HandleUpload) - authRouter.HandleFunc("/api/fs/download", staticWebServer.FileManager.HandleDownload) - authRouter.HandleFunc("/api/fs/newFolder", staticWebServer.FileManager.HandleNewFolder) - authRouter.HandleFunc("/api/fs/copy", staticWebServer.FileManager.HandleFileCopy) - authRouter.HandleFunc("/api/fs/move", staticWebServer.FileManager.HandleFileMove) - authRouter.HandleFunc("/api/fs/properties", staticWebServer.FileManager.HandleFileProperties) - authRouter.HandleFunc("/api/fs/del", staticWebServer.FileManager.HandleFileDelete) + authRouter.HandleFunc("/api/fs/list", staticWebServer.FileManager.HandleList, false) + authRouter.HandleFunc("/api/fs/upload", staticWebServer.FileManager.HandleUpload, false) + authRouter.HandleFunc("/api/fs/download", staticWebServer.FileManager.HandleDownload, false) + authRouter.HandleFunc("/api/fs/newFolder", staticWebServer.FileManager.HandleNewFolder, false) + authRouter.HandleFunc("/api/fs/copy", staticWebServer.FileManager.HandleFileCopy, false) + authRouter.HandleFunc("/api/fs/move", staticWebServer.FileManager.HandleFileMove, false) + authRouter.HandleFunc("/api/fs/properties", staticWebServer.FileManager.HandleFileProperties, false) + authRouter.HandleFunc("/api/fs/del", staticWebServer.FileManager.HandleFileDelete, false) } } // Register the APIs for Network Utilities functions func RegisterNetworkUtilsAPIs(authRouter *auth.RouterDef) { - authRouter.HandleFunc("/api/tools/ipscan", ipscan.HandleIpScan) - authRouter.HandleFunc("/api/tools/portscan", ipscan.HandleScanPort) - authRouter.HandleFunc("/api/tools/traceroute", netutils.HandleTraceRoute) - authRouter.HandleFunc("/api/tools/ping", netutils.HandlePing) - authRouter.HandleFunc("/api/tools/whois", netutils.HandleWhois) - authRouter.HandleFunc("/api/tools/webssh", HandleCreateProxySession) - authRouter.HandleFunc("/api/tools/websshSupported", HandleWebSshSupportCheck) - authRouter.HandleFunc("/api/tools/wol", HandleWakeOnLan) - authRouter.HandleFunc("/api/tools/smtp/get", HandleSMTPGet) - authRouter.HandleFunc("/api/tools/smtp/set", HandleSMTPSet) - authRouter.HandleFunc("/api/tools/smtp/admin", HandleAdminEmailGet) - authRouter.HandleFunc("/api/tools/smtp/test", HandleTestEmailSend) - authRouter.HandleFunc("/api/tools/fwdproxy/enable", forwardProxy.HandleToogle) - authRouter.HandleFunc("/api/tools/fwdproxy/port", forwardProxy.HandlePort) + authRouter.HandleFunc("/api/tools/ipscan", ipscan.HandleIpScan, false) + authRouter.HandleFunc("/api/tools/portscan", ipscan.HandleScanPort, false) + authRouter.HandleFunc("/api/tools/traceroute", netutils.HandleTraceRoute, false) + authRouter.HandleFunc("/api/tools/ping", netutils.HandlePing, false) + authRouter.HandleFunc("/api/tools/whois", netutils.HandleWhois, false) + authRouter.HandleFunc("/api/tools/webssh", HandleCreateProxySession, false) + authRouter.HandleFunc("/api/tools/websshSupported", HandleWebSshSupportCheck, false) + authRouter.HandleFunc("/api/tools/wol", HandleWakeOnLan, false) + authRouter.HandleFunc("/api/tools/smtp/get", HandleSMTPGet, false) + authRouter.HandleFunc("/api/tools/smtp/set", HandleSMTPSet, false) + authRouter.HandleFunc("/api/tools/smtp/admin", HandleAdminEmailGet, false) + authRouter.HandleFunc("/api/tools/smtp/test", HandleTestEmailSend, false) + authRouter.HandleFunc("/api/tools/fwdproxy/enable", forwardProxy.HandleToogle, false) + authRouter.HandleFunc("/api/tools/fwdproxy/port", forwardProxy.HandlePort, false) } func RegisterPluginAPIs(authRouter *auth.RouterDef) { - authRouter.HandleFunc("/api/plugins/list", pluginManager.HandleListPlugins) - authRouter.HandleFunc("/api/plugins/enable", pluginManager.HandleEnablePlugin) - authRouter.HandleFunc("/api/plugins/disable", pluginManager.HandleDisablePlugin) - authRouter.HandleFunc("/api/plugins/icon", pluginManager.HandleLoadPluginIcon) - authRouter.HandleFunc("/api/plugins/info", pluginManager.HandlePluginInfo) + authRouter.HandleFunc("/api/plugins/list", pluginManager.HandleListPlugins, false) + authRouter.HandleFunc("/api/plugins/enable", pluginManager.HandleEnablePlugin, false) + authRouter.HandleFunc("/api/plugins/disable", pluginManager.HandleDisablePlugin, false) + authRouter.HandleFunc("/api/plugins/icon", pluginManager.HandleLoadPluginIcon, false) + authRouter.HandleFunc("/api/plugins/info", pluginManager.HandlePluginInfo, false) - authRouter.HandleFunc("/api/plugins/groups/list", pluginManager.HandleListPluginGroups) - authRouter.HandleFunc("/api/plugins/groups/add", pluginManager.HandleAddPluginToGroup) - authRouter.HandleFunc("/api/plugins/groups/remove", pluginManager.HandleRemovePluginFromGroup) - authRouter.HandleFunc("/api/plugins/groups/deleteTag", pluginManager.HandleRemovePluginGroup) + authRouter.HandleFunc("/api/plugins/groups/list", pluginManager.HandleListPluginGroups, false) + authRouter.HandleFunc("/api/plugins/groups/add", pluginManager.HandleAddPluginToGroup, false) + authRouter.HandleFunc("/api/plugins/groups/remove", pluginManager.HandleRemovePluginFromGroup, false) + authRouter.HandleFunc("/api/plugins/groups/deleteTag", pluginManager.HandleRemovePluginGroup, false) - authRouter.HandleFunc("/api/plugins/store/list", pluginManager.HandleListDownloadablePlugins) - authRouter.HandleFunc("/api/plugins/store/resync", pluginManager.HandleResyncPluginList) - authRouter.HandleFunc("/api/plugins/store/install", pluginManager.HandleInstallPlugin) - authRouter.HandleFunc("/api/plugins/store/uninstall", pluginManager.HandleUninstallPlugin) + authRouter.HandleFunc("/api/plugins/store/list", pluginManager.HandleListDownloadablePlugins, false) + authRouter.HandleFunc("/api/plugins/store/resync", pluginManager.HandleResyncPluginList, false) + authRouter.HandleFunc("/api/plugins/store/install", pluginManager.HandleInstallPlugin, false) + authRouter.HandleFunc("/api/plugins/store/uninstall", pluginManager.HandleUninstallPlugin, false) // Developer options - authRouter.HandleFunc("/api/plugins/developer/enableAutoReload", pluginManager.HandleEnableHotReload) - authRouter.HandleFunc("/api/plugins/developer/setAutoReloadInterval", pluginManager.HandleSetHotReloadInterval) + authRouter.HandleFunc("/api/plugins/developer/enableAutoReload", pluginManager.HandleEnableHotReload, false) + authRouter.HandleFunc("/api/plugins/developer/setAutoReloadInterval", pluginManager.HandleSetHotReloadInterval, false) } // Register the APIs for Auth functions, due to scoping issue some functions are defined here @@ -372,17 +372,17 @@ func initAPIs(targetMux *http.ServeMux) { targetMux.HandleFunc("/api/account/new", HandleNewPasswordSetup) //Docker UX Optimizations - authRouter.HandleFunc("/api/docker/available", DockerUXOptimizer.HandleDockerAvailable) - authRouter.HandleFunc("/api/docker/containers", DockerUXOptimizer.HandleDockerContainersList) + authRouter.HandleFunc("/api/docker/available", DockerUXOptimizer.HandleDockerAvailable, false) + authRouter.HandleFunc("/api/docker/containers", DockerUXOptimizer.HandleDockerContainersList, false) //Others targetMux.HandleFunc("/api/info/x", HandleZoraxyInfo) - authRouter.HandleFunc("/api/info/geoip", HandleGeoIpLookup) - authRouter.HandleFunc("/api/conf/export", ExportConfigAsZip) - authRouter.HandleFunc("/api/conf/import", ImportConfigFromZip) - authRouter.HandleFunc("/api/log/list", LogViewer.HandleListLog) - authRouter.HandleFunc("/api/log/read", LogViewer.HandleReadLog) + authRouter.HandleFunc("/api/info/geoip", HandleGeoIpLookup, false) + authRouter.HandleFunc("/api/conf/export", ExportConfigAsZip, false) + authRouter.HandleFunc("/api/conf/import", ImportConfigFromZip, false) + authRouter.HandleFunc("/api/log/list", LogViewer.HandleListLog, false) + authRouter.HandleFunc("/api/log/read", LogViewer.HandleReadLog, false) //Debug - authRouter.HandleFunc("/api/info/pprof", pprof.Index) + authRouter.HandleFunc("/api/info/pprof", pprof.Index, false) } diff --git a/src/def.go b/src/def.go index 2e47323..9d550b7 100644 --- a/src/def.go +++ b/src/def.go @@ -144,6 +144,9 @@ var ( loadBalancer *loadbalance.RouteManager //Global scope loadbalancer, store the state of the lb routing pluginManager *plugins.Manager //Plugin manager for managing plugins + //Plugin auth related + pluginApiKeyManager *auth.APIKeyManager //API key manager for plugin authentication + //Authentication Provider forwardAuthRouter *forward.AuthRouter // Forward Auth router for Authelia/Authentik/etc authentication oauth2Router *oauth2.OAuth2Router //OAuth2Router router for OAuth2Router authentication diff --git a/src/mod/auth/auth.go b/src/mod/auth/auth.go index 350a972..d6ce56f 100644 --- a/src/mod/auth/auth.go +++ b/src/mod/auth/auth.go @@ -28,6 +28,8 @@ type AuthAgent struct { Database *db.Database LoginRedirectionHandler func(http.ResponseWriter, *http.Request) Logger *logger.Logger + //Plugin related + PluginAuthMiddleware *PluginAuthMiddleware //Plugin authentication middleware } type AuthEndpoints struct { @@ -39,7 +41,7 @@ type AuthEndpoints struct { } // Constructor -func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, allowReg bool, systemLogger *logger.Logger, loginRedirectionHandler func(http.ResponseWriter, *http.Request)) *AuthAgent { +func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, allowReg bool, systemLogger *logger.Logger, loginRedirectionHandler func(http.ResponseWriter, *http.Request), apiKeyManager *APIKeyManager) *AuthAgent { store := sessions.NewCookieStore(key) err := sysdb.NewTable("auth") if err != nil { @@ -47,6 +49,9 @@ func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, panic(err) } + //Initialize the plugin authentication middleware + pluginAuthMiddleware := NewPluginAuthMiddleware(apiKeyManager) + //Create a new AuthAgent object newAuthAgent := AuthAgent{ SessionName: sessionName, @@ -54,6 +59,7 @@ func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database, Database: sysdb, LoginRedirectionHandler: loginRedirectionHandler, Logger: systemLogger, + PluginAuthMiddleware: pluginAuthMiddleware, } //Return the authAgent diff --git a/src/mod/auth/plugin_apikey_manager.go b/src/mod/auth/plugin_apikey_manager.go new file mode 100644 index 0000000..7a06553 --- /dev/null +++ b/src/mod/auth/plugin_apikey_manager.go @@ -0,0 +1,152 @@ +package auth + +import ( + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "fmt" + "sync" + "time" + + "imuslab.com/zoraxy/mod/plugins/zoraxy_plugin" +) + +// PluginAPIKey represents an API key for a plugin +type PluginAPIKey struct { + PluginID string + APIKey string + PermittedEndpoints []zoraxy_plugin.PermittedAPIEndpoint // List of permitted API endpoints + CreatedAt time.Time +} + +// APIKeyManager manages API keys for plugins +type APIKeyManager struct { + keys map[string]*PluginAPIKey // key: API key, value: plugin info + mutex sync.RWMutex +} + +// NewAPIKeyManager creates a new API key manager +func NewAPIKeyManager() *APIKeyManager { + return &APIKeyManager{ + keys: make(map[string]*PluginAPIKey), + mutex: sync.RWMutex{}, + } +} + +// GenerateAPIKey generates a new API key for a plugin +func (m *APIKeyManager) GenerateAPIKey(pluginID string, permittedEndpoints []zoraxy_plugin.PermittedAPIEndpoint) (*PluginAPIKey, error) { + m.mutex.Lock() + defer m.mutex.Unlock() + + // Generate a cryptographically secure random key + bytes := make([]byte, 32) + if _, err := rand.Read(bytes); err != nil { + return nil, fmt.Errorf("failed to generate random bytes: %w", err) + } + + // Hash the random bytes to create the API key + hash := sha256.Sum256(bytes) + apiKey := hex.EncodeToString(hash[:]) + + // Create the plugin API key + pluginAPIKey := &PluginAPIKey{ + PluginID: pluginID, + APIKey: apiKey, + PermittedEndpoints: permittedEndpoints, + CreatedAt: time.Now(), + } + + // Store the API key + m.keys[apiKey] = pluginAPIKey + + return pluginAPIKey, nil +} + +// ValidateAPIKey validates an API key and returns the associated plugin information +func (m *APIKeyManager) ValidateAPIKey(apiKey string) (*PluginAPIKey, error) { + m.mutex.RLock() + defer m.mutex.RUnlock() + + pluginAPIKey, exists := m.keys[apiKey] + if !exists { + return nil, fmt.Errorf("invalid API key") + } + + return pluginAPIKey, nil +} + +// ValidateAPIKeyForEndpoint validates an API key for a specific endpoint +func (m *APIKeyManager) ValidateAPIKeyForEndpoint(endpoint string, method string, apiKey string) (*PluginAPIKey, error) { + pluginAPIKey, err := m.ValidateAPIKey(apiKey) + if err != nil { + return nil, err + } + + // Check if the endpoint is permitted + for _, permittedEndpoint := range pluginAPIKey.PermittedEndpoints { + if permittedEndpoint.Endpoint == endpoint { + return pluginAPIKey, nil + } + } + + return nil, fmt.Errorf("endpoint not permitted for this API key") +} + +// RevokeAPIKey revokes an API key +func (m *APIKeyManager) RevokeAPIKey(apiKey string) error { + m.mutex.Lock() + defer m.mutex.Unlock() + + if _, exists := m.keys[apiKey]; !exists { + return fmt.Errorf("API key not found") + } + + delete(m.keys, apiKey) + return nil +} + +// RevokeAPIKeysForPlugin revokes all API keys for a specific plugin +func (m *APIKeyManager) RevokeAPIKeysForPlugin(pluginID string) error { + m.mutex.Lock() + defer m.mutex.Unlock() + + keysToRemove := []string{} + for apiKey, pluginAPIKey := range m.keys { + if pluginAPIKey.PluginID == pluginID { + keysToRemove = append(keysToRemove, apiKey) + } + } + + for _, apiKey := range keysToRemove { + delete(m.keys, apiKey) + } + + return nil +} + +// GetAPIKeyForPlugin returns the API key for a plugin (if exists) +func (m *APIKeyManager) GetAPIKeyForPlugin(pluginID string) (*PluginAPIKey, error) { + m.mutex.RLock() + defer m.mutex.RUnlock() + + for _, pluginAPIKey := range m.keys { + if pluginAPIKey.PluginID == pluginID { + return pluginAPIKey, nil + } + } + + return nil, fmt.Errorf("no API key found for plugin") +} + +// ListAPIKeys returns all API keys (for debugging purposes) +func (m *APIKeyManager) ListAPIKeys() []*PluginAPIKey { + m.mutex.RLock() + defer m.mutex.RUnlock() + + keys := make([]*PluginAPIKey, 0, len(m.keys)) + for _, pluginAPIKey := range m.keys { + keys = append(keys, pluginAPIKey) + } + + return keys +} diff --git a/src/mod/auth/plugin_middleware.go b/src/mod/auth/plugin_middleware.go new file mode 100644 index 0000000..9be7cb8 --- /dev/null +++ b/src/mod/auth/plugin_middleware.go @@ -0,0 +1,77 @@ +// Handles the API-Key based authentication for plugins + +package auth + +import ( + "net/http" + "strings" +) + +// PluginAuthMiddleware provides authentication middleware for plugin API requests +type PluginAuthMiddleware struct { + apiKeyManager *APIKeyManager +} + +// NewPluginAuthMiddleware creates a new plugin authentication middleware +func NewPluginAuthMiddleware(apiKeyManager *APIKeyManager) *PluginAuthMiddleware { + return &PluginAuthMiddleware{ + apiKeyManager: apiKeyManager, + } +} + +// ValidatePluginAPIRequest validates an API request with plugin API key for a specific endpoint +func (m *PluginAuthMiddleware) ValidatePluginAPIRequest(endpoint string, method string, apiKey string) (*PluginAPIKey, error) { + return m.apiKeyManager.ValidateAPIKeyForEndpoint(endpoint, method, apiKey) +} + +// WrapHandler wraps an HTTP handler with plugin authentication middleware +func (m *PluginAuthMiddleware) WrapHandler(endpoint string, handler http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // First, remove any existing plugin authentication headers + r.Header.Del("X-Zoraxy-Plugin-ID") + r.Header.Del("X-Zoraxy-Plugin-Auth") + + // Check for API key in the Authorization header + authHeader := r.Header.Get("Authorization") + if authHeader == "" { + // No authorization header, proceed with normal authentication + handler(w, r) + return + } + + // Check if it's a plugin API key (Bearer token) + if !strings.HasPrefix(authHeader, "Bearer ") { + // Not a Bearer token, proceed with normal authentication + handler(w, r) + return + } + + // Extract the API key + apiKey := strings.TrimPrefix(authHeader, "Bearer ") + + // Validate the API key for this endpoint + pluginAPIKey, err := m.ValidatePluginAPIRequest(endpoint, r.Method, apiKey) + if err != nil { + // Invalid API key or endpoint not permitted + http.Error(w, "Unauthorized: Invalid API key or endpoint not permitted", http.StatusUnauthorized) + return + } + + // Add plugin information to the request context + r.Header.Set("X-Zoraxy-Plugin-ID", pluginAPIKey.PluginID) + r.Header.Set("X-Zoraxy-Plugin-Auth", "true") + + // Call the original handler + handler(w, r) + } +} + +// GetPluginIDFromRequest extracts the plugin ID from the request if authenticated via plugin API key +func GetPluginIDFromRequest(r *http.Request) string { + return r.Header.Get("X-Plugin-ID") +} + +// IsPluginAuthenticated checks if the request is authenticated via plugin API key +func IsPluginAuthenticated(r *http.Request) bool { + return r.Header.Get("X-Plugin-Auth") == "true" +} diff --git a/src/mod/auth/router.go b/src/mod/auth/router.go index 85f9dae..572df8e 100644 --- a/src/mod/auth/router.go +++ b/src/mod/auth/router.go @@ -25,7 +25,7 @@ func NewManagedHTTPRouter(option RouterOption) *RouterDef { } } -func (router *RouterDef) HandleFunc(endpoint string, handler func(http.ResponseWriter, *http.Request)) error { +func (router *RouterDef) HandleFunc(endpoint string, handler func(http.ResponseWriter, *http.Request), pluginAccessible bool) error { //Check if the endpoint already registered if _, exist := router.endpoints[endpoint]; exist { fmt.Println("WARNING! Duplicated registering of web endpoint: " + endpoint) @@ -34,31 +34,28 @@ func (router *RouterDef) HandleFunc(endpoint string, handler func(http.ResponseW authAgent := router.option.AuthAgent + authWrapper := func(w http.ResponseWriter, r *http.Request) { + //Check authentication of the user + X_Plugin_Auth := r.Header.Get("X-Zoraxy-Plugin-Auth") + if router.option.RequireAuth && !(pluginAccessible && X_Plugin_Auth == "true") { + authAgent.HandleCheckAuth(w, r, func(w http.ResponseWriter, r *http.Request) { + handler(w, r) + }) + } else { + handler(w, r) + } + } + + // if the endpoint is supposed to be plugin accessible, wrap it with plugin authentication middleware + if pluginAccessible { + authWrapper = router.option.AuthAgent.PluginAuthMiddleware.WrapHandler(endpoint, authWrapper) + } + //OK. Register handler if router.option.TargetMux == nil { - http.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) { - //Check authentication of the user - if router.option.RequireAuth { - authAgent.HandleCheckAuth(w, r, func(w http.ResponseWriter, r *http.Request) { - handler(w, r) - }) - } else { - handler(w, r) - } - - }) + http.HandleFunc(endpoint, authWrapper) } else { - router.option.TargetMux.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) { - //Check authentication of the user - if router.option.RequireAuth { - authAgent.HandleCheckAuth(w, r, func(w http.ResponseWriter, r *http.Request) { - handler(w, r) - }) - } else { - handler(w, r) - } - - }) + router.option.TargetMux.HandleFunc(endpoint, authWrapper) } router.endpoints[endpoint] = handler diff --git a/src/mod/plugins/lifecycle.go b/src/mod/plugins/lifecycle.go index 914acf7..cceff11 100644 --- a/src/mod/plugins/lifecycle.go +++ b/src/mod/plugins/lifecycle.go @@ -41,6 +41,18 @@ func (m *Manager) StartPlugin(pluginID string) error { Port: getRandomPortNumber(), RuntimeConst: *m.Options.SystemConst, } + + // Generate API key if the plugin has permitted endpoints + if len(thisPlugin.Spec.PermittedAPIEndpoints) > 0 { + apiKey, err := m.Options.APIKeyManager.GenerateAPIKey(thisPlugin.Spec.ID, thisPlugin.Spec.PermittedAPIEndpoints) + if err != nil { + return err + } + pluginConfiguration.APIKey = apiKey.APIKey + pluginConfiguration.ZoraxyPort = m.Options.ZoraxyPort + m.Log("Generated API key for plugin "+thisPlugin.Spec.Name, nil) + } + js, _ := json.Marshal(pluginConfiguration) //Start the plugin with given configuration @@ -270,6 +282,13 @@ func (m *Manager) StopPlugin(pluginID string) error { thisPlugin.Enabled = false thisPlugin.StopAllStaticPathRouters() thisPlugin.StopDynamicForwardRouter() + + //Clean up API key + err = m.Options.APIKeyManager.RevokeAPIKeysForPlugin(thisPlugin.Spec.ID) + if err != nil { + m.Log("Failed to revoke API keys for plugin "+thisPlugin.Spec.Name, err) + } + return nil } diff --git a/src/mod/plugins/typdef.go b/src/mod/plugins/typdef.go index 6b64068..befdf97 100644 --- a/src/mod/plugins/typdef.go +++ b/src/mod/plugins/typdef.go @@ -7,6 +7,7 @@ import ( "sync" "time" + "imuslab.com/zoraxy/mod/auth" "imuslab.com/zoraxy/mod/database" "imuslab.com/zoraxy/mod/dynamicproxy/dpcore" "imuslab.com/zoraxy/mod/info/logger" @@ -42,10 +43,14 @@ type ManagerOptions struct { /* Runtime */ SystemConst *zoraxyPlugin.RuntimeConstantValue //The system constant value + ZoraxyPort int //The port of the Zoraxy instance, used for API calls CSRFTokenGen func(*http.Request) string `json:"-"` //The CSRF token generator function Database *database.Database `json:"-"` Logger *logger.Logger `json:"-"` + /* API Key Management */ + APIKeyManager *auth.APIKeyManager `json:"-"` //The API key manager for the plugins + /* Development */ EnableHotReload bool //Check if the plugin file is changed and reload the plugin automatically HotReloadInterval int //The interval for checking the plugin file change, in seconds diff --git a/src/mod/plugins/zoraxy_plugin/zoraxy_plugin.go b/src/mod/plugins/zoraxy_plugin/zoraxy_plugin.go index 737e928..5398087 100644 --- a/src/mod/plugins/zoraxy_plugin/zoraxy_plugin.go +++ b/src/mod/plugins/zoraxy_plugin/zoraxy_plugin.go @@ -47,6 +47,12 @@ type RuntimeConstantValue struct { DevelopmentBuild bool `json:"development_build"` //Whether the Zoraxy is a development build or not } +type PermittedAPIEndpoint struct { + Method string `json:"method"` //HTTP method for the API endpoint (e.g., GET, POST) + Endpoint string `json:"endpoint"` //The API endpoint that the plugin can access + Reason string `json:"reason"` //The reason why the plugin needs to access this endpoint +} + /* IntroSpect Payload @@ -97,7 +103,10 @@ type IntroSpect struct { /* Subscriptions Settings */ SubscriptionPath string `json:"subscription_path"` //Subscription event path of your plugin (e.g. /notifyme), a POST request with SubscriptionEvent as body will be sent to this path when the event is triggered - SubscriptionsEvents map[string]string `json:"subscriptions_events"` //Subscriptions events of your plugin, see Zoraxy documentation for more details + SubscriptionsEvents map[string]string `json:"subscriptions_events"` //Subscriptions events of your plugin, paired with comments describing how the event is used, see Zoraxy documentation for more details + + /* API Access Control */ + PermittedAPIEndpoints []PermittedAPIEndpoint `json:"permitted_api_endpoints"` //List of API endpoints this plugin can access, and a description of why the plugin needs to access this endpoint } /* @@ -126,8 +135,10 @@ by the supplied values like starting a web server at given port that listens to 127.0.0.1:port */ type ConfigureSpec struct { - Port int `json:"port"` //Port to listen - RuntimeConst RuntimeConstantValue `json:"runtime_const"` //Runtime constant values + Port int `json:"port"` //Port to listen + RuntimeConst RuntimeConstantValue `json:"runtime_const"` //Runtime constant values + APIKey string `json:"api_key,omitempty"` //API key for accessing Zoraxy APIs, if the plugin has permitted endpoints + ZoraxyPort int `json:"zoraxy_port,omitempty"` //The port that Zoraxy is running on, used for making API calls to Zoraxy //To be expanded } diff --git a/src/start.go b/src/start.go index 359da3a..1cc55bf 100644 --- a/src/start.go +++ b/src/start.go @@ -3,6 +3,7 @@ package main import ( "log" "net/http" + "net/netip" "os" "runtime" "strconv" @@ -90,6 +91,7 @@ func startupSequence() { os.MkdirAll(CONF_HTTP_PROXY, 0775) //Create an auth agent + pluginApiKeyManager = auth.NewAPIKeyManager() sessionKey, err := auth.GetSessionKey(sysdb, SystemWideLogger) if err != nil { log.Fatal(err) @@ -97,7 +99,7 @@ func startupSequence() { authAgent = auth.NewAuthenticationAgent(SYSTEM_NAME, []byte(sessionKey), sysdb, true, SystemWideLogger, func(w http.ResponseWriter, r *http.Request) { //Not logged in. Redirecting to login page http.Redirect(w, r, "/login.html", http.StatusTemporaryRedirect) - }) + }, pluginApiKeyManager) //Create a TLS certificate manager tlsCertManager, err = tlscert.NewManager(CONF_CERT_STORE, SystemWideLogger) @@ -313,11 +315,18 @@ func startupSequence() { */ pluginFolder := *path_plugin pluginFolder = strings.TrimSuffix(pluginFolder, "/") + ZoraxyAddrPort, err := netip.ParseAddrPort(*webUIPort) + ZoraxyPort := 8000 + if err == nil && ZoraxyAddrPort.IsValid() && ZoraxyAddrPort.Port() > 0 { + ZoraxyPort = int(ZoraxyAddrPort.Port()) + } pluginManager = plugins.NewPluginManager(&plugins.ManagerOptions{ PluginDir: pluginFolder, Database: sysdb, Logger: SystemWideLogger, PluginGroupsConfig: CONF_PLUGIN_GROUPS, + APIKeyManager: pluginApiKeyManager, + ZoraxyPort: ZoraxyPort, CSRFTokenGen: func(r *http.Request) string { return csrf.Token(r) },