Updated GAN features

+ Added add controller as memeber feature
+ Deprecated aroz subservice support
This commit is contained in:
Toby Chui 2024-02-16 21:16:14 +08:00
parent 059b0a2e1c
commit 216b53f224
10 changed files with 212 additions and 82 deletions

View File

@ -83,8 +83,6 @@ Usage of zoraxy:
Disable authentication for management interface
-port string
Management web interface listening port (default ":8000")
-rpt string
Reserved
-sshlb
Allow loopback web ssh connection (DANGER)
-version
@ -109,39 +107,6 @@ If you already have an upstream reverse proxy server in place with permission ma
*Note: For security reaons, you should only enable no-auth if you are running Zoraxy in a trusted environment or with another authentication management proxy in front.*
#### Use with ArozOS
The [ArozOS](https://arozos.com) subservice is a built-in, permission-managed, reverse proxy server. To use Zoraxy with ArozOS, connect to your ArozOS host via SSH and use the following command to install Zoraxy:
```bash
# cd into your ArozOS subservice folder. Sometimes it is under ~/arozos/src/subservice.
cd ~/arozos/subservices
mkdir zoraxy
cd ./zoraxy
# Download the release binary from Github release.
wget {binary executable link from release page}
# Set permission. Change this if required.
sudo chmod 775 -R ./
# Start zoraxy to see if the downloaded arch is correct.
./zoraxy
# After unzipping, press Ctrl + C to kill it.
# Rename it to validate the ArozOS subservice binary format.
mv ./zoraxy zoraxy_linux_amd64
# If you are using SBCs with a different CPU arch, use the following names:
# mv ./zoraxy zoraxy_linux_arm
# mv ./zoraxy zoraxy_linux_arm64
# Restart ArozOS
sudo systemctl restart arozos
```
To start the module, go to System Settings > Modules > Subservice and enable it in the menu. You should be able to see a new module named "Zoraxy" pop up in the start menu.
## Screenshots
![](img/screenshots/1.png)

View File

@ -119,6 +119,8 @@ func initAPIs() {
authRouter.HandleFunc("/api/gan/network/name", ganManager.HandleNetworkNaming)
//authRouter.HandleFunc("/api/gan/network/detail", ganManager.HandleNetworkDetails)
authRouter.HandleFunc("/api/gan/network/setRange", ganManager.HandleSetRanges)
authRouter.HandleFunc("/api/gan/network/join", ganManager.HandleServerJoinNetwork)
authRouter.HandleFunc("/api/gan/network/leave", ganManager.HandleServerLeaveNetwork)
authRouter.HandleFunc("/api/gan/members/list", ganManager.HandleMemberList)
authRouter.HandleFunc("/api/gan/members/ip", ganManager.HandleMemberIP)
authRouter.HandleFunc("/api/gan/members/name", ganManager.HandleMemberNaming)

View File

@ -13,7 +13,6 @@ import (
"github.com/google/uuid"
"imuslab.com/zoraxy/mod/acme"
"imuslab.com/zoraxy/mod/aroz"
"imuslab.com/zoraxy/mod/auth"
"imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/dynamicproxy/redirection"
@ -35,6 +34,7 @@ import (
)
// General flags
var webUIPort = flag.String("port", ":8000", "Management web interface listening port")
var noauth = flag.Bool("noauth", false, "Disable authentication for management interface")
var showver = flag.Bool("version", false, "Show version of this server")
var allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
@ -63,7 +63,6 @@ var (
/*
Handler Modules
*/
handler *aroz.ArozHandler //Handle arozos managed permission system
sysdb *database.Database //System database
authAgent *auth.AuthAgent //Authentication agent
tlsCertManager *tlscert.Manager //TLS / SSL management
@ -128,20 +127,8 @@ func ShutdownSeq() {
}
func main() {
//Start the aoModule pipeline (which will parse the flags as well). Pass in the module launch information
handler = aroz.HandleFlagParse(aroz.ServiceInfo{
Name: name,
Desc: "Dynamic Reverse Proxy Server",
Group: "Network",
IconPath: "zoraxy/img/small_icon.png",
Version: version,
StartDir: "zoraxy/index.html",
SupportFW: true,
LaunchFWDir: "zoraxy/index.html",
SupportEmb: false,
InitFWSize: []int{1080, 580},
})
//Parse startup flags
flag.Parse()
if *showver {
fmt.Println(name + " - Version " + version)
os.Exit(0)
@ -166,7 +153,7 @@ func main() {
startupSequence()
//Initiate management interface APIs
requireAuth = !(*noauth || handler.IsUsingExternalPermissionManager())
requireAuth = !(*noauth)
initAPIs()
//Start the reverse proxy server in go routine
@ -179,8 +166,8 @@ func main() {
//Start the finalize sequences
finalSequence()
SystemWideLogger.Println("Zoraxy started. Visit control panel at http://localhost" + handler.Port)
err = http.ListenAndServe(handler.Port, nil)
SystemWideLogger.Println("Zoraxy started. Visit control panel at http://localhost" + *webUIPort)
err = http.ListenAndServe(*webUIPort, nil)
if err != nil {
log.Fatal(err)

View File

@ -207,7 +207,7 @@ func (m *NetworkManager) HandleSetRanges(w http.ResponseWriter, r *http.Request)
utils.SendOK(w)
}
//Handle listing of network members. Set details=true for listing all details
// Handle listing of network members. Set details=true for listing all details
func (m *NetworkManager) HandleMemberList(w http.ResponseWriter, r *http.Request) {
netid, err := utils.GetPara(r, "netid")
if err != nil {
@ -241,7 +241,7 @@ func (m *NetworkManager) HandleMemberList(w http.ResponseWriter, r *http.Request
}
}
//Handle Authorization of members
// Handle Authorization of members
func (m *NetworkManager) HandleMemberAuthorization(w http.ResponseWriter, r *http.Request) {
netid, err := utils.PostPara(r, "netid")
if err != nil {
@ -281,7 +281,7 @@ func (m *NetworkManager) HandleMemberAuthorization(w http.ResponseWriter, r *htt
}
}
//Handle Delete or Add IP for a member in a network
// Handle Delete or Add IP for a member in a network
func (m *NetworkManager) HandleMemberIP(w http.ResponseWriter, r *http.Request) {
netid, err := utils.PostPara(r, "netid")
if err != nil {
@ -356,7 +356,7 @@ func (m *NetworkManager) HandleMemberIP(w http.ResponseWriter, r *http.Request)
}
}
//Handle naming for members
// Handle naming for members
func (m *NetworkManager) HandleMemberNaming(w http.ResponseWriter, r *http.Request) {
netid, err := utils.PostPara(r, "netid")
if err != nil {
@ -391,7 +391,7 @@ func (m *NetworkManager) HandleMemberNaming(w http.ResponseWriter, r *http.Reque
}
}
//Handle delete of a given memver
// Handle delete of a given memver
func (m *NetworkManager) HandleMemberDelete(w http.ResponseWriter, r *http.Request) {
netid, err := utils.PostPara(r, "netid")
if err != nil {
@ -426,3 +426,79 @@ func (m *NetworkManager) HandleMemberDelete(w http.ResponseWriter, r *http.Reque
utils.SendOK(w)
}
// Check if a given network id is a network hosted on this zoraxy node
func (m *NetworkManager) IsLocalGAN(networkId string) bool {
networks, err := m.listNetworkIds()
if err != nil {
return false
}
for _, network := range networks {
if network == networkId {
return true
}
}
return false
}
// Handle server instant joining a given network
func (m *NetworkManager) HandleServerJoinNetwork(w http.ResponseWriter, r *http.Request) {
netid, err := utils.PostPara(r, "netid")
if err != nil {
utils.SendErrorResponse(w, "net id not set")
return
}
//Check if the target network is a network hosted on this server
if !m.IsLocalGAN(netid) {
utils.SendErrorResponse(w, "given network is not a GAN hosted on this node")
return
}
if m.memberExistsInNetwork(netid, m.ControllerID) {
utils.SendErrorResponse(w, "controller already inside network")
return
}
//Join the network
err = m.joinNetwork(netid)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}
// Handle server instant leaving a given network
func (m *NetworkManager) HandleServerLeaveNetwork(w http.ResponseWriter, r *http.Request) {
netid, err := utils.PostPara(r, "netid")
if err != nil {
utils.SendErrorResponse(w, "net id not set")
return
}
//Check if the target network is a network hosted on this server
if !m.IsLocalGAN(netid) {
utils.SendErrorResponse(w, "given network is not a GAN hosted on this node")
return
}
//Leave the network
err = m.leaveNetwork(netid)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
//Remove it from target network if it is authorized
err = m.deleteMember(netid, m.ControllerID)
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}

View File

@ -117,7 +117,7 @@ type MemberInfo struct {
VRev int `json:"vRev"`
}
//Get the zerotier node info from local service
// Get the zerotier node info from local service
func getControllerInfo(token string, apiPort int) (*NodeInfo, error) {
url := "http://localhost:" + strconv.Itoa(apiPort) + "/status"
@ -187,7 +187,7 @@ func (m *NetworkManager) createNetwork() (*NetworkInfo, error) {
return &networkInfo, nil
}
//List network details
// List network details
func (m *NetworkManager) getNetworkInfoById(networkId string) (*NetworkInfo, error) {
req, err := http.NewRequest("GET", os.ExpandEnv("http://localhost:"+strconv.Itoa(m.apiPort)+"/controller/network/"+networkId+"/"), nil)
if err != nil {
@ -249,7 +249,7 @@ func (m *NetworkManager) setNetworkInfoByID(networkId string, newNetworkInfo *Ne
return nil
}
//List network IDs
// List network IDs
func (m *NetworkManager) listNetworkIds() ([]string, error) {
req, err := http.NewRequest("GET", "http://localhost:"+strconv.Itoa(m.apiPort)+"/controller/network/", nil)
if err != nil {
@ -281,7 +281,7 @@ func (m *NetworkManager) listNetworkIds() ([]string, error) {
return networkIds, nil
}
//wrapper for checking if a network id exists
// wrapper for checking if a network id exists
func (m *NetworkManager) networkExists(networkId string) bool {
networkIds, err := m.listNetworkIds()
if err != nil {
@ -297,7 +297,7 @@ func (m *NetworkManager) networkExists(networkId string) bool {
return false
}
//delete a network
// delete a network
func (m *NetworkManager) deleteNetwork(networkID string) error {
url := "http://localhost:" + strconv.Itoa(m.apiPort) + "/controller/network/" + networkID + "/"
client := &http.Client{}
@ -330,8 +330,8 @@ func (m *NetworkManager) deleteNetwork(networkID string) error {
return nil
}
//Configure network
//Example: configureNetwork(netid, "192.168.192.1", "192.168.192.254", "192.168.192.0/24")
// Configure network
// Example: configureNetwork(netid, "192.168.192.1", "192.168.192.254", "192.168.192.0/24")
func (m *NetworkManager) configureNetwork(networkID string, ipRangeStart string, ipRangeEnd string, routeTarget string) error {
url := "http://localhost:" + strconv.Itoa(m.apiPort) + "/controller/network/" + networkID + "/"
data := map[string]interface{}{
@ -545,7 +545,7 @@ func (m *NetworkManager) memberExistsInNetwork(netid string, memid string) bool
return false
}
//Get a network memeber info by netid and memberid
// Get a network memeber info by netid and memberid
func (m *NetworkManager) getNetworkMemberInfo(netid string, memberid string) (*MemberInfo, error) {
req, err := http.NewRequest("GET", "http://localhost:"+strconv.Itoa(m.apiPort)+"/controller/network/"+netid+"/member/"+memberid, nil)
if err != nil {
@ -573,7 +573,7 @@ func (m *NetworkManager) getNetworkMemberInfo(netid string, memberid string) (*M
return thisMemeberInfo, nil
}
//Set the authorization state of a member
// Set the authorization state of a member
func (m *NetworkManager) AuthorizeMember(netid string, memberid string, setAuthorized bool) error {
url := "http://localhost:" + strconv.Itoa(m.apiPort) + "/controller/network/" + netid + "/member/" + memberid
payload := []byte(`{"authorized": true}`)
@ -600,7 +600,7 @@ func (m *NetworkManager) AuthorizeMember(netid string, memberid string, setAutho
return nil
}
//Delete a member from the network
// Delete a member from the network
func (m *NetworkManager) deleteMember(netid string, memid string) error {
req, err := http.NewRequest("DELETE", "http://localhost:"+strconv.Itoa(m.apiPort)+"/controller/network/"+netid+"/member/"+memid, nil)
if err != nil {
@ -620,3 +620,45 @@ func (m *NetworkManager) deleteMember(netid string, memid string) error {
return nil
}
// Make the host to join a given network
func (m *NetworkManager) joinNetwork(netid string) error {
req, err := http.NewRequest("POST", "http://localhost:"+strconv.Itoa(m.apiPort)+"/network/"+netid, nil)
if err != nil {
return err
}
req.Header.Set("X-Zt1-Auth", os.ExpandEnv(m.authToken))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return errors.New("network error. Status code: " + strconv.Itoa(resp.StatusCode))
}
return nil
}
// Make the host to leave a given network
func (m *NetworkManager) leaveNetwork(netid string) error {
req, err := http.NewRequest("DELETE", "http://localhost:"+strconv.Itoa(m.apiPort)+"/network/"+netid, nil)
if err != nil {
return err
}
req.Header.Set("X-Zt1-Auth", os.ExpandEnv(m.authToken))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return errors.New("network error. Status code: " + strconv.Itoa(resp.StatusCode))
}
return nil
}

View File

@ -140,7 +140,7 @@ func startupSequence() {
*/
if *allowMdnsScanning {
portInt, err := strconv.Atoi(strings.Split(handler.Port, ":")[1])
portInt, err := strconv.Atoi(strings.Split(*webUIPort, ":")[1])
if err != nil {
portInt = 8000
}

View File

@ -24,7 +24,6 @@
<div class="content">
<div class="header" style="font-size: 1.2em;" id="ganodeCount">0</div>
<div class="description" id="connectedNodes" count="0">Connected Nodes</div>
</div>
</div>
</div>
@ -219,8 +218,8 @@
}
//Bind event to tab switch
tabSwitchEventBind["gan"] = function(){
//Bind event to tab switch
tabSwitchEventBind["gan"] = function(){
//On switch over to this page, load info
listGANet();
initGANetID();

View File

@ -61,7 +61,7 @@
<h2>Members</h2>
<p>To join this network using command line, type <code>sudo zerotier-cli join <span class="ganetID"></span></code> on your device terminal</p>
<div class="ui checkbox" style="margin-bottom: 1em;">
<input id="showUnauthorizedMembers" type="checkbox" onchange="changeUnauthorizedVisibility(this.checked);">
<input id="showUnauthorizedMembers" type="checkbox" onchange="changeUnauthorizedVisibility(this.checked);" checked>
<label>Show Unauthorized Members</label>
</div>
<div class="" style="overflow-x: auto;">
@ -84,6 +84,11 @@
</tbody>
</table>
</div>
<div class="ui divider"></div>
<h4>Add Controller as Member</h4>
<p>Optionally you can add the network controller (ZeroTier running on the Zoraxy node) as member for cross GAN reverse proxy to bypass NAT limitations.</p>
<button class="ui basic small button addControllerToNetworkBtn" onclick="ganAddControllerToNetwork(this);"><i class="green add icon"></i> Add Controller as Member</button>
<button class="ui basic small button removeControllerFromNetworkBtn" onclick="ganRemoveControllerFromNetwork(this);"><i class="red sign-out icon"></i> Remove Controller from Member</button>
<br><br>
</div>
<script>
@ -355,7 +360,10 @@
url: '/api/gan/members/list?netid=' + currentGANetID + '&detail=true',
type: 'GET',
success: function(data) {
const tableBody = $('#networkMemeberTable');
let tableBody = $('#networkMemeberTable');
if (tableBody.length == 0){
return;
}
data.sort((a, b) => a.address.localeCompare(b.address));
//Check if the new object equal to the old one
if (objectEqual(currentGANMemberList, data) && !forceUpdate){
@ -592,6 +600,55 @@
}
//Add and remove this controller node to network as member
function ganAddControllerToNetwork(){
$(".addControllerToNetworkBtn").addClass("disabled");
$(".addControllerToNetworkBtn").addClass("loading");
$.ajax({
url: "/api/gan/network/join",
method: "POST",
data: {
netid:currentGANetID,
},
success: function(data){
$(".addControllerToNetworkBtn").removeClass("disabled");
$(".addControllerToNetworkBtn").removeClass("loading");
if (data.error != undefined){
msgbox(data.error, false, 6000);
}else{
msgbox("Controller joint " + currentGANetID);
}
setTimeout(function(){
renderMemeberTable(true);
}, 3000)
}
});
}
function ganRemoveControllerFromNetwork(){
$(".removeControllerFromNetworkBtn").addClass("disabled");
$(".removeControllerFromNetworkBtn").addClass("loading");
$.ajax({
url: "/api/gan/network/leave",
method: "POST",
data: {
netid:currentGANetID,
},
success: function(data){
if (data.error != undefined){
msgbox(data.error, false, 6000);
}else{
msgbox("Controller left " + currentGANetID);
}
renderMemeberTable(true);
$(".removeControllerFromNetworkBtn").removeClass("disabled");
$(".removeControllerFromNetworkBtn").removeClass("loading");
}
});
}
//Entry points
function initGanetDetails(ganetId){
currentGANetID = ganetId;
@ -612,6 +669,11 @@
}
//Switch from other tabs back to this, exit to GAN list
tabSwitchEventBind["gan"] = function(){
exitToGanList();
}
//Exit point
function exitToGanList(){
$("#gan").load("./components/gan.html", function(){

View File

@ -284,10 +284,11 @@
$("#mainmenu").find(".item").removeClass("active");
$(targetBtn).addClass("active");
$(".functiontab").hide();
if (tabSwitchEventBind[tabID]){
tabSwitchEventBind[tabID]();
}
$("#" + tabID).fadeIn('fast', function(){
if (tabSwitchEventBind[tabID]){
tabSwitchEventBind[tabID]();
}
});
$('html,body').animate({scrollTop: 0}, 'fast');
window.location.hash = tabID;

View File

@ -356,10 +356,6 @@ body{
color: var(--item_color);
}
.ui.menu .item:hover{
color: var(--item_color_select) !important;
}
.ui.segment{
box-shadow: none !important;
}