mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-09-08 21:26:39 +02:00
Added #263
- Added IP / CIDR as Basic Auth exclusion rule - Fixed side frame not closing when open proxy rule editor bug
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/auth"
|
||||
"imuslab.com/zoraxy/mod/netutils"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -70,9 +71,36 @@ func handleBasicAuth(w http.ResponseWriter, r *http.Request, pe *ProxyEndpoint)
|
||||
if len(pe.AuthenticationProvider.BasicAuthExceptionRules) > 0 {
|
||||
//Check if the current path matches the exception rules
|
||||
for _, exceptionRule := range pe.AuthenticationProvider.BasicAuthExceptionRules {
|
||||
if strings.HasPrefix(r.RequestURI, exceptionRule.PathPrefix) {
|
||||
//This path is excluded from basic auth
|
||||
return nil
|
||||
exceptionType := exceptionRule.RuleType
|
||||
switch exceptionType {
|
||||
case AuthExceptionType_Paths:
|
||||
if strings.HasPrefix(r.RequestURI, exceptionRule.PathPrefix) {
|
||||
//This path is excluded from basic auth
|
||||
return nil
|
||||
}
|
||||
case AuthExceptionType_CIDR:
|
||||
requesterIp := netutils.GetRequesterIP(r)
|
||||
if requesterIp != "" {
|
||||
if requesterIp == exceptionRule.CIDR {
|
||||
// This IP is excluded from basic auth
|
||||
return nil
|
||||
}
|
||||
|
||||
wildcardMatch := netutils.MatchIpWildcard(requesterIp, exceptionRule.CIDR)
|
||||
if wildcardMatch {
|
||||
// This IP is excluded from basic auth
|
||||
return nil
|
||||
}
|
||||
|
||||
cidrMatch := netutils.MatchIpCIDR(requesterIp, exceptionRule.CIDR)
|
||||
if cidrMatch {
|
||||
// This IP is excluded from basic auth
|
||||
return nil
|
||||
}
|
||||
}
|
||||
default:
|
||||
//Unknown exception type, skip this rule
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -106,9 +106,18 @@ type BasicAuthUnhashedCredentials struct {
|
||||
Password string
|
||||
}
|
||||
|
||||
type AuthExceptionType int
|
||||
|
||||
const (
|
||||
AuthExceptionType_Paths AuthExceptionType = iota //Path exception, match by path prefix
|
||||
AuthExceptionType_CIDR //CIDR exception, match by CIDR
|
||||
)
|
||||
|
||||
// Paths to exclude in basic auth enabled proxy handler
|
||||
type BasicAuthExceptionRule struct {
|
||||
PathPrefix string
|
||||
RuleType AuthExceptionType //The type of the exception rule
|
||||
PathPrefix string //Path prefix to match, e.g. /api/v1/
|
||||
CIDR string //CIDR to match, e.g. 192.168.1.0/24 or IP address, e.g. 192.168.1.1
|
||||
}
|
||||
|
||||
/* Routing Rule Data Structures */
|
||||
|
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@@ -956,10 +957,10 @@ func UpdateProxyBasicAuthCredentials(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// List, Update or Remove the exception paths for basic auth.
|
||||
func ListProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
ep, err := utils.GetPara(r, "ep")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "Invalid ep given")
|
||||
@@ -981,6 +982,7 @@ func ListProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
js, _ := json.Marshal(results)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -991,10 +993,9 @@ func AddProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
matchingPrefix, err := utils.PostPara(r, "prefix")
|
||||
exceptionType, err := utils.PostInt(r, "type")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "Invalid matching prefix given")
|
||||
return
|
||||
exceptionType = 0x00 //Default to paths
|
||||
}
|
||||
|
||||
//Load the target proxy object from router
|
||||
@@ -1004,26 +1005,100 @@ func AddProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
//Check if the prefix starts with /. If not, prepend it
|
||||
if !strings.HasPrefix(matchingPrefix, "/") {
|
||||
matchingPrefix = "/" + matchingPrefix
|
||||
}
|
||||
|
||||
//Add a new exception rule if it is not already exists
|
||||
alreadyExists := false
|
||||
for _, thisExceptionRule := range targetProxy.AuthenticationProvider.BasicAuthExceptionRules {
|
||||
if thisExceptionRule.PathPrefix == matchingPrefix {
|
||||
alreadyExists = true
|
||||
break
|
||||
switch exceptionType {
|
||||
case 0x00:
|
||||
matchingPrefix, err := utils.PostPara(r, "prefix")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "Invalid matching prefix given")
|
||||
return
|
||||
}
|
||||
}
|
||||
if alreadyExists {
|
||||
utils.SendErrorResponse(w, "This matching path already exists")
|
||||
|
||||
//Check if the prefix starts with /. If not, prepend it
|
||||
if !strings.HasPrefix(matchingPrefix, "/") {
|
||||
matchingPrefix = "/" + matchingPrefix
|
||||
}
|
||||
|
||||
//Add a new exception rule if it is not already exists
|
||||
alreadyExists := false
|
||||
for _, thisExceptionRule := range targetProxy.AuthenticationProvider.BasicAuthExceptionRules {
|
||||
if thisExceptionRule.PathPrefix == matchingPrefix {
|
||||
alreadyExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if alreadyExists {
|
||||
utils.SendErrorResponse(w, "This matching path already exists")
|
||||
return
|
||||
}
|
||||
targetProxy.AuthenticationProvider.BasicAuthExceptionRules = append(targetProxy.AuthenticationProvider.BasicAuthExceptionRules, &dynamicproxy.BasicAuthExceptionRule{
|
||||
RuleType: dynamicproxy.AuthExceptionType_Paths,
|
||||
PathPrefix: strings.TrimSpace(matchingPrefix),
|
||||
})
|
||||
|
||||
case 0x01:
|
||||
matchingCIDR, err := utils.PostPara(r, "cidr")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "Invalid matching CIDR given")
|
||||
return
|
||||
}
|
||||
|
||||
// Accept CIDR, IP address, or wildcard like 192.168.0.*
|
||||
matchingCIDR = strings.TrimSpace(matchingCIDR)
|
||||
isValid := false
|
||||
|
||||
// Check if it's a valid CIDR
|
||||
if _, _, err := net.ParseCIDR(matchingCIDR); err == nil {
|
||||
isValid = true
|
||||
} else if ip := net.ParseIP(matchingCIDR); ip != nil {
|
||||
// Valid IP address
|
||||
isValid = true
|
||||
} else if strings.Contains(matchingCIDR, "*") {
|
||||
// Accept wildcard like 192.168.0.*
|
||||
parts := strings.Split(matchingCIDR, ".")
|
||||
if len(parts) == 4 && parts[3] == "*" {
|
||||
// Check first 3 parts are numbers 0-255
|
||||
validParts := true
|
||||
for i := 0; i < 3; i++ {
|
||||
n, err := strconv.Atoi(parts[i])
|
||||
if err != nil || n < 0 || n > 255 {
|
||||
validParts = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if validParts {
|
||||
isValid = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !isValid {
|
||||
utils.SendErrorResponse(w, "Invalid CIDR, IP, or wildcard given")
|
||||
return
|
||||
}
|
||||
|
||||
//Add a new exception rule if it is not already exists
|
||||
alreadyExists := false
|
||||
for _, thisExceptionRule := range targetProxy.AuthenticationProvider.BasicAuthExceptionRules {
|
||||
if thisExceptionRule.CIDR == matchingCIDR {
|
||||
alreadyExists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if alreadyExists {
|
||||
utils.SendErrorResponse(w, "This matching CIDR already exists")
|
||||
return
|
||||
}
|
||||
targetProxy.AuthenticationProvider.BasicAuthExceptionRules = append(targetProxy.AuthenticationProvider.BasicAuthExceptionRules, &dynamicproxy.BasicAuthExceptionRule{
|
||||
RuleType: dynamicproxy.AuthExceptionType_CIDR,
|
||||
CIDR: strings.TrimSpace(matchingCIDR),
|
||||
})
|
||||
|
||||
default:
|
||||
//Invalid exception type given
|
||||
utils.SendErrorResponse(w, "Invalid exception type given")
|
||||
return
|
||||
|
||||
}
|
||||
targetProxy.AuthenticationProvider.BasicAuthExceptionRules = append(targetProxy.AuthenticationProvider.BasicAuthExceptionRules, &dynamicproxy.BasicAuthExceptionRule{
|
||||
PathPrefix: strings.TrimSpace(matchingPrefix),
|
||||
})
|
||||
|
||||
//Save configs to runtime and file
|
||||
targetProxy.UpdateToRuntime()
|
||||
@@ -1040,9 +1115,39 @@ func RemoveProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
exceptionType, err := utils.PostInt(r, "type")
|
||||
if err != nil {
|
||||
exceptionType = 0x00 //Default to paths
|
||||
}
|
||||
|
||||
matchingPrefix, err := utils.PostPara(r, "prefix")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "Invalid matching prefix given")
|
||||
matchingPrefix = ""
|
||||
}
|
||||
|
||||
matchingCIDR, err := utils.PostPara(r, "cidr")
|
||||
if err != nil {
|
||||
matchingCIDR = ""
|
||||
}
|
||||
|
||||
var typeToCheck dynamicproxy.AuthExceptionType
|
||||
switch exceptionType {
|
||||
case 0x01:
|
||||
typeToCheck = dynamicproxy.AuthExceptionType_CIDR
|
||||
//Check if the CIDR is valid
|
||||
if matchingCIDR == "" {
|
||||
utils.SendErrorResponse(w, "Invalid matching CIDR given")
|
||||
return
|
||||
}
|
||||
case 0x00:
|
||||
fallthrough //For backward compatibility
|
||||
default:
|
||||
typeToCheck = dynamicproxy.AuthExceptionType_Paths
|
||||
//Check if the prefix is valid
|
||||
if matchingPrefix == "" {
|
||||
utils.SendErrorResponse(w, "Invalid matching prefix given")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1056,10 +1161,22 @@ func RemoveProxyBasicAuthExceptionPaths(w http.ResponseWriter, r *http.Request)
|
||||
newExceptionRuleList := []*dynamicproxy.BasicAuthExceptionRule{}
|
||||
matchingExists := false
|
||||
for _, thisExceptionalRule := range targetProxy.AuthenticationProvider.BasicAuthExceptionRules {
|
||||
if thisExceptionalRule.PathPrefix != matchingPrefix {
|
||||
newExceptionRuleList = append(newExceptionRuleList, thisExceptionalRule)
|
||||
} else {
|
||||
matchingExists = true
|
||||
switch typeToCheck {
|
||||
case dynamicproxy.AuthExceptionType_CIDR:
|
||||
if thisExceptionalRule.CIDR != matchingCIDR {
|
||||
newExceptionRuleList = append(newExceptionRuleList, thisExceptionalRule)
|
||||
} else {
|
||||
matchingExists = true
|
||||
}
|
||||
case dynamicproxy.AuthExceptionType_Paths:
|
||||
fallthrough //For backward compatibility
|
||||
default:
|
||||
if thisExceptionalRule.PathPrefix != matchingPrefix {
|
||||
newExceptionRuleList = append(newExceptionRuleList, thisExceptionalRule)
|
||||
} else {
|
||||
matchingExists = true
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1200,6 +1200,9 @@
|
||||
//Populate all the information in the proxy editor
|
||||
populateAndBindEventsToHTTPProxyEditor(subd);
|
||||
|
||||
//Hide all previously opened editor side-frame wrapper
|
||||
hideEditorSideWrapper();
|
||||
|
||||
//Show the first rpconfig
|
||||
$("#httprpEditModal .rpconfig_content").hide();
|
||||
$("#httprpEditModal .rpconfig_content[rpcfg='downstream']").show();
|
||||
|
@@ -46,24 +46,37 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<h3 class="ui header">Authentication Exclusion Paths</h3>
|
||||
<h3 class="ui header">Authentication Exclusion</h3>
|
||||
<div class="scrolling content ui form">
|
||||
<p>Exclude specific directories / paths which contains the following subpath prefix from authentication. Useful if you are hosting services require remote API access.</p>
|
||||
<p>Exclude <b>specific directories which contains the following subpath prefix</b> or <b>IP / CIDR</b> from authentication. Useful if you are hosting services require remote API access.</p>
|
||||
<table class="ui basic very compacted unstackable celled table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Path Prefix</th>
|
||||
<th>Exception Type</th>
|
||||
<th>Path Prefix / CIDR</th>
|
||||
<th>Remove</th>
|
||||
</tr></thead>
|
||||
<tbody id="exclusionPaths">
|
||||
<tr>
|
||||
<td colspan="2"><i class="ui green circle check icon"></i> No Path Excluded</td>
|
||||
<td colspan="3"><i class="ui green circle check icon"></i> No Exclusion Rule</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="field">
|
||||
<input id="newExclusionPath" type="text" placeholder="/public/api/" autocomplete="off">
|
||||
<small>Make sure you add the tailing slash for only selecting the files / folder inside that path.</small>
|
||||
<div class="fields" style="margin-bottom: 0.4em;">
|
||||
<div class="field" style="margin-bottom: 0.4em;">
|
||||
<select class="ui basic fluid dropdown" id="exceptionTypeDropdown" style="margin-right: 1em;">
|
||||
<option value="path">Path Prefix</option>
|
||||
<option value="ip">IP / CIDR</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field" id="exclusionPathField">
|
||||
<input id="newExclusionPath" type="text" placeholder="/public/api/" autocomplete="off">
|
||||
<small>Make sure you add the trailing slash!</small>
|
||||
</div>
|
||||
<div class="field" id="exclusionIPField" style="display: none;">
|
||||
<input id="newExclusionIP" type="text" placeholder="192.168.1.0/24" autocomplete="off">
|
||||
<small>Enter a valid IP address or CIDR block.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field" >
|
||||
<button class="ui basic button" onclick="addExceptionPath();"><i class="yellow add icon"></i> Add Exception</button>
|
||||
@@ -99,6 +112,19 @@
|
||||
console.log("Unable to load endpoint data from hash")
|
||||
}
|
||||
}
|
||||
// Initialize the dropdown
|
||||
$('#exceptionTypeDropdown').dropdown({
|
||||
onChange: function(value, text, $selectedItem) {
|
||||
if (value === 'ip') {
|
||||
$('#exclusionPathField').hide();
|
||||
$('#exclusionIPField').show();
|
||||
} else {
|
||||
$('#exclusionPathField').show();
|
||||
$('#exclusionIPField').hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
$('#exceptionTypeDropdown').dropdown('set selected', 'path');
|
||||
|
||||
function loadBasicAuthCredentials(uuid){
|
||||
$.ajax({
|
||||
@@ -161,17 +187,31 @@
|
||||
}
|
||||
|
||||
function addExceptionPath(){
|
||||
// Retrieve the username and password input values
|
||||
var newExclusionPathMatchingPrefix = $('#newExclusionPath').val().trim();
|
||||
if (newExclusionPathMatchingPrefix == ""){
|
||||
parent.msgbox("Matching prefix cannot be empty!", false, 5000);
|
||||
return;
|
||||
|
||||
let exceptionType = $('#exceptionTypeDropdown').val() == "path" ? 0x0 : 0x1;
|
||||
let newExclusionPathMatchingPrefix = $('#newExclusionPath').val().trim();
|
||||
let newExclusionIP = $('#newExclusionIP').val().trim();
|
||||
if (exceptionType == 0x0){
|
||||
//Check if the path is empty
|
||||
|
||||
if (newExclusionPathMatchingPrefix == ""){
|
||||
parent.msgbox("Matching prefix cannot be empty!", false, 5000);
|
||||
return;
|
||||
}
|
||||
}else{
|
||||
//Check if the CIDR is empty
|
||||
if (newExclusionIP == ""){
|
||||
parent.msgbox("Matching CIDR cannot be empty!", false, 5000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
$.cjax({
|
||||
url: "/api/proxy/auth/exceptions/add",
|
||||
data:{
|
||||
"type":exceptionType,
|
||||
ep: editingEndpoint.ep,
|
||||
prefix: newExclusionPathMatchingPrefix
|
||||
prefix: newExclusionPathMatchingPrefix,
|
||||
cidr: newExclusionIP
|
||||
},
|
||||
method: "POST",
|
||||
success: function(data){
|
||||
@@ -181,6 +221,7 @@
|
||||
initExceptionPaths();
|
||||
parent.msgbox("New exception path added", true);
|
||||
$('#newExclusionPath').val("");
|
||||
$('#newExclusionIP').val("");
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -188,12 +229,29 @@
|
||||
|
||||
function removeExceptionPath(object){
|
||||
let matchingPrefix = $(object).attr("prefix");
|
||||
let exceptionType = parseInt($(object).attr("etype"));
|
||||
if (exceptionType == undefined || matchingPrefix == undefined){
|
||||
parent.msgbox("Invalid exception path data", false, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
let reqPayload = {
|
||||
"type": exceptionType,
|
||||
ep: editingEndpoint.ep,
|
||||
};
|
||||
|
||||
if (exceptionType == 0x0){
|
||||
reqPayload.prefix = matchingPrefix;
|
||||
}else if (exceptionType == 0x1){
|
||||
reqPayload.cidr = matchingPrefix;
|
||||
}else{
|
||||
parent.msgbox("Unknown exception type", false, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
$.cjax({
|
||||
url: "/api/proxy/auth/exceptions/delete",
|
||||
data:{
|
||||
ep: editingEndpoint.ep,
|
||||
prefix: matchingPrefix
|
||||
},
|
||||
data: reqPayload,
|
||||
method: "POST",
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
@@ -206,6 +264,17 @@
|
||||
});
|
||||
}
|
||||
|
||||
function exceptionTypeToString(type){
|
||||
switch(type){
|
||||
case 0x0:
|
||||
return "Path Prefix";
|
||||
case 0x1:
|
||||
return "IP or CIDR";
|
||||
default:
|
||||
return "Unknown Type";
|
||||
}
|
||||
}
|
||||
|
||||
//Load exception paths from server
|
||||
function initExceptionPaths(){
|
||||
$.get(`/api/proxy/auth/exceptions/list?ptype=${editingEndpoint.ept}&ep=${editingEndpoint.ep}`, function(data){
|
||||
@@ -214,14 +283,15 @@
|
||||
}else{
|
||||
if (data.length == 0){
|
||||
$("#exclusionPaths").html(` <tr>
|
||||
<td colspan="2"><i class="ui green circle check icon"></i> No Path Excluded</td>
|
||||
<td colspan="3"><i class="ui green circle check icon"></i> No Path Excluded</td>
|
||||
</tr>`);
|
||||
}else{
|
||||
$("#exclusionPaths").html("");
|
||||
data.forEach(function(rule){
|
||||
$("#exclusionPaths").append(` <tr>
|
||||
<td>${rule.PathPrefix}</td>
|
||||
<td><button class="ui red basic mini circular icon button" onclick="removeExceptionPath(this);" prefix="${rule.PathPrefix}"><i class="ui red times icon"></i></button></td>
|
||||
<td>${exceptionTypeToString(rule.RuleType)}</td>
|
||||
<td>${rule.PathPrefix || rule.CIDR }</td>
|
||||
<td><button class="ui red basic mini circular icon button" onclick="removeExceptionPath(this);" etype="${rule.RuleType}" prefix="${rule.PathPrefix || rule.CIDR}"><i class="ui red times icon"></i></button></td>
|
||||
</tr>`);
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user