diff --git a/controllers/dnsHttpController.go b/controllers/dnsHttpController.go index e5f5d425..d575b17c 100644 --- a/controllers/dnsHttpController.go +++ b/controllers/dnsHttpController.go @@ -3,7 +3,6 @@ package controller import ( "encoding/json" "net/http" - "strings" "github.com/go-playground/validator/v10" "github.com/gorilla/mux" @@ -15,14 +14,14 @@ import ( func dnsHandlers(r *mux.Router) { - r.HandleFunc("/api/dns", securityCheckDNS(true, true, http.HandlerFunc(getAllDNS))).Methods("GET") - r.HandleFunc("/api/dns/adm/{network}/nodes", securityCheckDNS(false, true, http.HandlerFunc(getNodeDNS))).Methods("GET") - r.HandleFunc("/api/dns/adm/{network}/custom", securityCheckDNS(false, true, http.HandlerFunc(getCustomDNS))).Methods("GET") - r.HandleFunc("/api/dns/adm/{network}", securityCheckDNS(false, true, http.HandlerFunc(getDNS))).Methods("GET") - r.HandleFunc("/api/dns/{network}", securityCheckDNS(false, false, http.HandlerFunc(createDNS))).Methods("POST") - r.HandleFunc("/api/dns/adm/pushdns", securityCheckDNS(false, false, http.HandlerFunc(pushDNS))).Methods("POST") - r.HandleFunc("/api/dns/{network}/{domain}", securityCheckDNS(false, false, http.HandlerFunc(deleteDNS))).Methods("DELETE") - r.HandleFunc("/api/dns/{network}/{domain}", securityCheckDNS(false, false, http.HandlerFunc(updateDNS))).Methods("PUT") + r.HandleFunc("/api/dns", securityCheck(true, http.HandlerFunc(getAllDNS))).Methods("GET") + r.HandleFunc("/api/dns/adm/{network}/nodes", securityCheck(false, http.HandlerFunc(getNodeDNS))).Methods("GET") + r.HandleFunc("/api/dns/adm/{network}/custom", securityCheck(false, http.HandlerFunc(getCustomDNS))).Methods("GET") + r.HandleFunc("/api/dns/adm/{network}", securityCheck(false, http.HandlerFunc(getDNS))).Methods("GET") + r.HandleFunc("/api/dns/{network}", securityCheck(false, http.HandlerFunc(createDNS))).Methods("POST") + r.HandleFunc("/api/dns/adm/pushdns", securityCheck(false, http.HandlerFunc(pushDNS))).Methods("POST") + r.HandleFunc("/api/dns/{network}/{domain}", securityCheck(false, http.HandlerFunc(deleteDNS))).Methods("DELETE") + r.HandleFunc("/api/dns/{network}/{domain}", securityCheck(false, http.HandlerFunc(updateDNS))).Methods("PUT") } //Gets all nodes associated with network, including pending nodes @@ -386,20 +385,6 @@ func ValidateDNSUpdate(change models.DNSEntry, entry models.DNSEntry) error { return err == nil }) - // _ = v.RegisterValidation("name_valid", func(fl validator.FieldLevel) bool { - // isvalid := functions.NameInDNSCharSet(entry.Name) - // notEmptyCheck := entry.Name != "" - // return isvalid && notEmptyCheck - // }) - // - // _ = v.RegisterValidation("address_valid", func(fl validator.FieldLevel) bool { - // isValid := true - // if entry.Address != "" { - // isValid = functions.IsIpNet(entry.Address) - // } - // return isValid - // }) - err := v.Struct(change) if err != nil { @@ -409,42 +394,3 @@ func ValidateDNSUpdate(change models.DNSEntry, entry models.DNSEntry) error { } return err } - -//Security check DNS is middleware for every DNS function and just checks to make sure that its the master or dns token calling -//Only admin should have access to all these network-level actions -//DNS token should have access to only read functions -func securityCheckDNS(reqAdmin bool, allowDNSToken bool, next http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var errorResponse = models.ErrorResponse{ - Code: http.StatusUnauthorized, Message: "W1R3: It's not you it's me.", - } - - var params = mux.Vars(r) - bearerToken := r.Header.Get("Authorization") - if allowDNSToken && authenticateDNSToken(bearerToken) { - r.Header.Set("user", "nameserver") - networks, _ := json.Marshal([]string{ALL_NETWORK_ACCESS}) - r.Header.Set("networks", string(networks)) - next.ServeHTTP(w, r) - } else { - err, networks, username := SecurityCheck(reqAdmin, params["networkname"], bearerToken) - if err != nil { - if strings.Contains(err.Error(), "does not exist") { - errorResponse.Code = http.StatusNotFound - } - errorResponse.Message = err.Error() - returnErrorResponse(w, r, errorResponse) - return - } - networksJson, err := json.Marshal(&networks) - if err != nil { - errorResponse.Message = err.Error() - returnErrorResponse(w, r, errorResponse) - return - } - r.Header.Set("user", username) - r.Header.Set("networks", string(networksJson)) - next.ServeHTTP(w, r) - } - } -} diff --git a/controllers/networkHttpController.go b/controllers/networkHttpController.go index 75e7cdba..1dc439fc 100644 --- a/controllers/networkHttpController.go +++ b/controllers/networkHttpController.go @@ -36,101 +36,6 @@ func networkHandlers(r *mux.Router) { r.HandleFunc("/api/networks/{networkname}/keys/{name}", securityCheck(false, http.HandlerFunc(deleteAccessKey))).Methods("DELETE") } -//Security check is middleware for every function and just checks to make sure that its the master calling -//Only admin should have access to all these network-level actions -//or maybe some Users once implemented -func securityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var errorResponse = models.ErrorResponse{ - Code: http.StatusUnauthorized, Message: "W1R3: It's not you it's me.", - } - if strings.Contains(r.RequestURI, "/dns") && r.Method == "GET" { - - } - - var params = mux.Vars(r) - bearerToken := r.Header.Get("Authorization") - err, networks, username := SecurityCheck(reqAdmin, params["networkname"], bearerToken) - if err != nil { - if strings.Contains(err.Error(), "does not exist") { - errorResponse.Code = http.StatusNotFound - } - errorResponse.Message = err.Error() - returnErrorResponse(w, r, errorResponse) - return - } - networksJson, err := json.Marshal(&networks) - if err != nil { - errorResponse.Message = err.Error() - returnErrorResponse(w, r, errorResponse) - return - } - r.Header.Set("user", username) - r.Header.Set("networks", string(networksJson)) - next.ServeHTTP(w, r) - } -} - -// SecurityCheck - checks token stuff -func SecurityCheck(reqAdmin bool, netname string, token string) (error, []string, string) { - - var hasBearer = true - var tokenSplit = strings.Split(token, " ") - var authToken = "" - - if len(tokenSplit) < 2 { - hasBearer = false - } else { - authToken = tokenSplit[1] - } - userNetworks := []string{} - //all endpoints here require master so not as complicated - isMasterAuthenticated := authenticateMaster(authToken) - username := "" - if !hasBearer || !isMasterAuthenticated { - userName, networks, isadmin, err := logic.VerifyUserToken(authToken) - username = userName - if err != nil { - return errors.New("error verifying user token"), nil, username - } - if !isadmin && reqAdmin { - return errors.New("you are unauthorized to access this endpoint"), nil, username - } - userNetworks = networks - if isadmin { - userNetworks = []string{ALL_NETWORK_ACCESS} - } else { - networkexists, err := functions.NetworkExists(netname) - if err != nil && !database.IsEmptyRecord(err) { - return err, nil, "" - } - if netname != "" && !networkexists { - return errors.New("this network does not exist"), nil, "" - } - } - } else if isMasterAuthenticated { - userNetworks = []string{ALL_NETWORK_ACCESS} - } - if len(userNetworks) == 0 { - userNetworks = append(userNetworks, NO_NETWORKS_PRESENT) - } - return nil, userNetworks, username -} - -//Consider a more secure way of setting master key -func authenticateMaster(tokenString string) bool { - return tokenString == servercfg.GetMasterKey() -} - -//Consider a more secure way of setting master key -func authenticateDNSToken(tokenString string) bool { - tokens := strings.Split(tokenString, " ") - if len(tokens) < 2 { - return false - } - return tokens[1] == servercfg.GetDNSKey() -} - //simple get all networks function func getNetworks(w http.ResponseWriter, r *http.Request) { diff --git a/controllers/security.go b/controllers/security.go index 3841aae3..554c942c 100644 --- a/controllers/security.go +++ b/controllers/security.go @@ -14,45 +14,6 @@ import ( "github.com/gravitl/netmaker/servercfg" ) -//Security check DNS is middleware for every DNS function and just checks to make sure that its the master or dns token calling -//Only admin should have access to all these network-level actions -//DNS token should have access to only read functions -func securityCheck(reqAdmin bool, allowDNSToken bool, next http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var errorResponse = models.ErrorResponse{ - Code: http.StatusUnauthorized, Message: "W1R3: It's not you it's me.", - } - - var params = mux.Vars(r) - bearerToken := r.Header.Get("Authorization") - if allowDNSToken && authenticateDNSToken(bearerToken) { - r.Header.Set("user", "nameserver") - networks, _ := json.Marshal([]string{ALL_NETWORK_ACCESS}) - r.Header.Set("networks", string(networks)) - next.ServeHTTP(w, r) - } else { - err, networks, username := SecurityCheck(reqAdmin, params["networkname"], bearerToken) - if err != nil { - if strings.Contains(err.Error(), "does not exist") { - errorResponse.Code = http.StatusNotFound - } - errorResponse.Message = err.Error() - returnErrorResponse(w, r, errorResponse) - return - } - networksJson, err := json.Marshal(&networks) - if err != nil { - errorResponse.Message = err.Error() - returnErrorResponse(w, r, errorResponse) - return - } - r.Header.Set("user", username) - r.Header.Set("networks", string(networksJson)) - next.ServeHTTP(w, r) - } - } -} - func securityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var errorResponse = models.ErrorResponse{ @@ -61,6 +22,14 @@ func securityCheck(reqAdmin bool, next http.Handler) http.HandlerFunc { var params = mux.Vars(r) bearerToken := r.Header.Get("Authorization") + if strings.Contains(r.RequestURI, "/dns") && strings.ToUpper(r.Method) == "GET" && authenticateDNSToken(bearerToken) { + // do dns stuff + r.Header.Set("user", "nameserver") + networks, _ := json.Marshal([]string{ALL_NETWORK_ACCESS}) + r.Header.Set("networks", string(networks)) + next.ServeHTTP(w, r) + } + err, networks, username := SecurityCheck(reqAdmin, params["networkname"], bearerToken) if err != nil { if strings.Contains(err.Error(), "does not exist") { @@ -141,3 +110,48 @@ func authenticateDNSToken(tokenString string) bool { } return tokens[1] == servercfg.GetDNSKey() } + +// ValidateUserToken - self explained +func ValidateUserToken(token string, user string, adminonly bool) error { + var tokenSplit = strings.Split(token, " ") + //I put this in in case the user doesn't put in a token at all (in which case it's empty) + //There's probably a smarter way of handling this. + var authToken = "928rt238tghgwe@TY@$Y@#WQAEGB2FC#@HG#@$Hddd" + + if len(tokenSplit) > 1 { + authToken = tokenSplit[1] + } else { + return errors.New("Missing Auth Token.") + } + + username, _, isadmin, err := logic.VerifyUserToken(authToken) + if err != nil { + return errors.New("Error Verifying Auth Token") + } + isAuthorized := false + if adminonly { + isAuthorized = isadmin + } else { + isAuthorized = username == user || isadmin + } + if !isAuthorized { + return errors.New("You are unauthorized to access this endpoint.") + } + + return nil +} + +func continueIfUserMatch(next http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var errorResponse = models.ErrorResponse{ + Code: http.StatusUnauthorized, Message: "W1R3: This doesn't look like you.", + } + var params = mux.Vars(r) + var requestedUser = params["username"] + if requestedUser != r.Header.Get("user") { + returnErrorResponse(w, r, errorResponse) + return + } + next.ServeHTTP(w, r) + } +} diff --git a/controllers/userHttpController.go b/controllers/userHttpController.go index a5d6a24b..f08328c7 100644 --- a/controllers/userHttpController.go +++ b/controllers/userHttpController.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "net/http" - "strings" "github.com/gorilla/mux" "github.com/gravitl/netmaker/auth" @@ -20,13 +19,13 @@ func userHandlers(r *mux.Router) { r.HandleFunc("/api/users/adm/hasadmin", hasAdmin).Methods("GET") r.HandleFunc("/api/users/adm/createadmin", createAdmin).Methods("POST") r.HandleFunc("/api/users/adm/authenticate", authenticateUser).Methods("POST") - r.HandleFunc("/api/users/{username}", authorizeUser(http.HandlerFunc(updateUser))).Methods("PUT") - r.HandleFunc("/api/users/networks/{username}", authorizeUserAdm(http.HandlerFunc(updateUserNetworks))).Methods("PUT") - r.HandleFunc("/api/users/{username}/adm", authorizeUserAdm(http.HandlerFunc(updateUserAdm))).Methods("PUT") - r.HandleFunc("/api/users/{username}", authorizeUserAdm(http.HandlerFunc(createUser))).Methods("POST") - r.HandleFunc("/api/users/{username}", authorizeUser(http.HandlerFunc(deleteUser))).Methods("DELETE") - r.HandleFunc("/api/users/{username}", authorizeUser(http.HandlerFunc(getUser))).Methods("GET") - r.HandleFunc("/api/users", authorizeUserAdm(http.HandlerFunc(getUsers))).Methods("GET") + r.HandleFunc("/api/users/{username}", securityCheck(false, continueIfUserMatch(http.HandlerFunc(updateUser)))).Methods("PUT") + r.HandleFunc("/api/users/networks/{username}", securityCheck(true, http.HandlerFunc(updateUserNetworks))).Methods("PUT") + r.HandleFunc("/api/users/{username}/adm", securityCheck(true, http.HandlerFunc(updateUserAdm))).Methods("PUT") + r.HandleFunc("/api/users/{username}", securityCheck(true, http.HandlerFunc(createUser))).Methods("POST") + r.HandleFunc("/api/users/{username}", securityCheck(false, continueIfUserMatch(http.HandlerFunc(deleteUser)))).Methods("DELETE") + r.HandleFunc("/api/users/{username}", securityCheck(false, continueIfUserMatch(http.HandlerFunc(getUser)))).Methods("GET") + r.HandleFunc("/api/users", securityCheck(true, http.HandlerFunc(getUsers))).Methods("GET") r.HandleFunc("/api/oauth/login", auth.HandleAuthLogin).Methods("GET") r.HandleFunc("/api/oauth/callback", auth.HandleAuthCallback).Methods("GET") } @@ -82,79 +81,6 @@ func authenticateUser(response http.ResponseWriter, request *http.Request) { response.Write(successJSONResponse) } -// The middleware for most requests to the API -// They all pass through here first -// This will validate the JWT (or check for master token) -// This will also check against the authNetwork and make sure the node should be accessing that endpoint, -// even if it's technically ok -// This is kind of a poor man's RBAC. There's probably a better/smarter way. -// TODO: Consider better RBAC implementations -func authorizeUser(next http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - var params = mux.Vars(r) - - // get the auth token - bearerToken := r.Header.Get("Authorization") - username := params["username"] - err := ValidateUserToken(bearerToken, username, false) - if err != nil { - returnErrorResponse(w, r, formatError(err, "unauthorized")) - return - } - r.Header.Set("user", username) - next.ServeHTTP(w, r) - } -} - -func authorizeUserAdm(next http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - var params = mux.Vars(r) - - //get the auth token - bearerToken := r.Header.Get("Authorization") - username := params["username"] - err := ValidateUserToken(bearerToken, username, true) - if err != nil { - returnErrorResponse(w, r, formatError(err, "unauthorized")) - return - } - r.Header.Set("user", username) - next.ServeHTTP(w, r) - } -} - -// ValidateUserToken - self explained -func ValidateUserToken(token string, user string, adminonly bool) error { - var tokenSplit = strings.Split(token, " ") - //I put this in in case the user doesn't put in a token at all (in which case it's empty) - //There's probably a smarter way of handling this. - var authToken = "928rt238tghgwe@TY@$Y@#WQAEGB2FC#@HG#@$Hddd" - - if len(tokenSplit) > 1 { - authToken = tokenSplit[1] - } else { - return errors.New("Missing Auth Token.") - } - - username, _, isadmin, err := logic.VerifyUserToken(authToken) - if err != nil { - return errors.New("Error Verifying Auth Token") - } - isAuthorized := false - if adminonly { - isAuthorized = isadmin - } else { - isAuthorized = username == user || isadmin - } - if !isAuthorized { - return errors.New("You are unauthorized to access this endpoint.") - } - - return nil -} - func hasAdmin(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json")