From bbca20e463caabfcd327fc81efa3c740f04a3e92 Mon Sep 17 00:00:00 2001 From: Max Ma Date: Wed, 28 Aug 2024 15:28:07 +0200 Subject: [PATCH 1/5] NET-1565:fix extClient ip conflict issue (#3082) * fix extClient ip conflict issue * Update users.go --------- Co-authored-by: Abhishek K --- logic/networks.go | 32 ++++++++++++++++++++++++-------- pro/controllers/users.go | 2 -- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/logic/networks.go b/logic/networks.go index b87c6db83..14ad794e5 100644 --- a/logic/networks.go +++ b/logic/networks.go @@ -42,19 +42,35 @@ func SetAllocatedIpMap() error { pMap := map[string]net.IP{} netName := v.NetID + //nodes nodes, err := GetNetworkNodes(netName) if err != nil { slog.Error("could not load node for network", netName, "error", err.Error()) - continue - } - - for _, n := range nodes { + } else { + for _, n := range nodes { - if n.Address.IP != nil { - pMap[n.Address.IP.String()] = n.Address.IP + if n.Address.IP != nil { + pMap[n.Address.IP.String()] = n.Address.IP + } + if n.Address6.IP != nil { + pMap[n.Address6.IP.String()] = n.Address6.IP + } } - if n.Address6.IP != nil { - pMap[n.Address6.IP.String()] = n.Address6.IP + + } + + //extClients + extClients, err := GetNetworkExtClients(netName) + if err != nil { + slog.Error("could not load extClient for network", netName, "error", err.Error()) + } else { + for _, extClient := range extClients { + if extClient.Address != "" { + pMap[extClient.Address] = net.ParseIP(extClient.Address) + } + if extClient.Address6 != "" { + pMap[extClient.Address6] = net.ParseIP(extClient.Address6) + } } } diff --git a/pro/controllers/users.go b/pro/controllers/users.go index 393b28955..c5076d3dd 100644 --- a/pro/controllers/users.go +++ b/pro/controllers/users.go @@ -849,7 +849,6 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) { return } userGwNodes := proLogic.GetUserRAGNodes(*user) - logger.Log(0, fmt.Sprintf("1. User Gw Nodes: %+v", userGwNodes)) for _, extClient := range allextClients { node, ok := userGwNodes[extClient.IngressGatewayID] if !ok { @@ -885,7 +884,6 @@ func getUserRemoteAccessGwsV1(w http.ResponseWriter, r *http.Request) { delete(userGwNodes, node.ID.String()) } } - logger.Log(0, fmt.Sprintf("2. User Gw Nodes: %+v", userGwNodes)) // add remaining gw nodes to resp for gwID := range userGwNodes { node, err := logic.GetNodeByID(gwID) From 5ad21606f25f502489f15e1a5b87a8fcc5f21956 Mon Sep 17 00:00:00 2001 From: Sayan Mallick Date: Sun, 1 Sep 2024 22:06:49 +0530 Subject: [PATCH 2/5] Delete Droplet update --- .github/workflows/deletedroplets.yml | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deletedroplets.yml b/.github/workflows/deletedroplets.yml index 040190384..63f258244 100644 --- a/.github/workflows/deletedroplets.yml +++ b/.github/workflows/deletedroplets.yml @@ -37,11 +37,25 @@ jobs: - name: delete droplets if: success() || failure() run: | - sleep 15m - curl -X DELETE \ + sleep 5m + response=$(curl -X DELETE \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ - "https://api.digitalocean.com/v2/droplets?tag_name=$TAG" + -w "\n%{http_code}" \ + "https://api.digitalocean.com/v2/droplets?tag_name=$TAG") + + status_code=$(echo "$response" | tail -n1) + body=$(echo "$response" | sed '$d') + + echo "Response body: $body" + echo "Status code: $status_code" + + if [ "$status_code" -eq 204 ]; then + echo "Droplets deleted successfully" + else + echo "Failed to delete droplets. Status code: $status_code" + exit 1 + fi env: DIGITALOCEAN_TOKEN: ${{ secrets.DIGITALOCEAN_TOKEN }} TAG: ${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }} From ebce98448c76c19d0d7159743692dcd6b9ab359d Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 2 Sep 2024 09:23:28 +0530 Subject: [PATCH 3/5] use github apis to fetch user email --- models/user_mgmt.go | 21 +++++------ pro/auth/azure-ad.go | 40 ++++++++++++++------- pro/auth/github.go | 84 +++++++++++++++++++++++++++++++++++++------- 3 files changed, 110 insertions(+), 35 deletions(-) diff --git a/models/user_mgmt.go b/models/user_mgmt.go index 3efa81bf1..a928f5282 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -138,16 +138,17 @@ type UserGroup struct { // User struct - struct for Users type User struct { - UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"` - Password string `json:"password" bson:"password" validate:"required,min=5"` - IsAdmin bool `json:"isadmin" bson:"isadmin"` // deprecated - IsSuperAdmin bool `json:"issuperadmin"` // deprecated - RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` // deprecated - AuthType AuthType `json:"auth_type"` - UserGroups map[UserGroupID]struct{} `json:"user_group_ids"` - PlatformRoleID UserRoleID `json:"platform_role_id"` - NetworkRoles map[NetworkID]map[UserRoleID]struct{} `json:"network_roles"` - LastLoginTime time.Time `json:"last_login_time"` + UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"` + ExternalProviderID string `json:"external_provider_id"` + Password string `json:"password" bson:"password" validate:"required,min=5"` + IsAdmin bool `json:"isadmin" bson:"isadmin"` // deprecated + IsSuperAdmin bool `json:"issuperadmin"` // deprecated + RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` // deprecated + AuthType AuthType `json:"auth_type"` + UserGroups map[UserGroupID]struct{} `json:"user_group_ids"` + PlatformRoleID UserRoleID `json:"platform_role_id"` + NetworkRoles map[NetworkID]map[UserRoleID]struct{} `json:"network_roles"` + LastLoginTime time.Time `json:"last_login_time"` } type ReturnUserWithRolesAndGroups struct { diff --git a/pro/auth/azure-ad.go b/pro/auth/azure-ad.go index c41a96d81..fbe588ad2 100644 --- a/pro/auth/azure-ad.go +++ b/pro/auth/azure-ad.go @@ -3,6 +3,7 @@ package auth import ( "context" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -60,7 +61,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { var content, err = getAzureUserInfo(rState, rCode) if err != nil { logger.Log(1, "error when getting user info from azure:", err.Error()) - if strings.Contains(err.Error(), "invalid oauth state") { + if strings.Contains(err.Error(), "invalid oauth state") || strings.Contains(err.Error(), "failed to fetch user email from SSO state") { handleOauthNotValid(w) return } @@ -74,12 +75,23 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { inviteExists = true } // check if user approval is already pending - if !inviteExists && logic.IsPendingUser(content.UserPrincipalName) { + if !inviteExists && logic.IsPendingUser(content.Email) { handleOauthUserSignUpApprovalPending(w) return } - - _, err = logic.GetUser(content.UserPrincipalName) + // if user exists with provider ID, convert them into email ID + user, err := logic.GetUser(content.UserPrincipalName) + if err == nil { + _, err := logic.GetUser(content.Email) + if err != nil { + user.UserName = content.Email + user.ExternalProviderID = content.UserPrincipalName + database.DeleteRecord(database.USERS_TABLE_NAME, content.UserPrincipalName) + d, _ := json.Marshal(user) + database.Insert(user.UserName, string(d), database.USERS_TABLE_NAME) + } + } + _, err = logic.GetUser(content.Email) if err != nil { if database.IsEmptyRecord(err) { // user must not exist, so try to make one if inviteExists { @@ -89,20 +101,20 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - user.UserName = content.UserPrincipalName // override username with azure id + user.ExternalProviderID = content.UserPrincipalName if err = logic.CreateUser(&user); err != nil { handleSomethingWentWrong(w) return } logic.DeleteUserInvite(content.Email) - logic.DeletePendingUser(content.UserPrincipalName) + logic.DeletePendingUser(content.Email) } else { - if !isEmailAllowed(content.UserPrincipalName) { + if !isEmailAllowed(content.Email) { handleOauthUserNotAllowedToSignUp(w) return } err = logic.InsertPendingUser(&models.User{ - UserName: content.UserPrincipalName, + UserName: content.Email, }) if err != nil { handleSomethingWentWrong(w) @@ -116,7 +128,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { return } } - user, err := logic.GetUser(content.UserPrincipalName) + user, err = logic.GetUser(content.Email) if err != nil { handleOauthUserNotFound(w) return @@ -136,7 +148,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { } // send a netmaker jwt token var authRequest = models.UserAuthParams{ - UserName: content.UserPrincipalName, + UserName: content.Email, Password: newPass, } @@ -146,8 +158,8 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { return } - logger.Log(1, "completed azure OAuth sigin in for", content.UserPrincipalName) - http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?login="+jwt+"&user="+content.UserPrincipalName, http.StatusPermanentRedirect) + logger.Log(1, "completed azure OAuth sigin in for", content.Email) + http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?login="+jwt+"&user="+content.Email, http.StatusPermanentRedirect) } func getAzureUserInfo(state string, code string) (*OAuthUser, error) { @@ -187,6 +199,10 @@ func getAzureUserInfo(state string, code string) (*OAuthUser, error) { if userInfo.Email == "" { userInfo.Email = getUserEmailFromClaims(token.AccessToken) } + if userInfo.Email == "" { + err = errors.New("failed to fetch user email from SSO state") + return userInfo, err + } return userInfo, nil } diff --git a/pro/auth/github.go b/pro/auth/github.go index 2b65ee2fb..1cb52cf92 100644 --- a/pro/auth/github.go +++ b/pro/auth/github.go @@ -3,6 +3,7 @@ package auth import ( "context" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -60,7 +61,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { var content, err = getGithubUserInfo(rState, rCode) if err != nil { logger.Log(1, "error when getting user info from github:", err.Error()) - if strings.Contains(err.Error(), "invalid oauth state") { + if strings.Contains(err.Error(), "invalid oauth state") || strings.Contains(err.Error(), "failed to fetch user email from SSO state") { handleOauthNotValid(w) return } @@ -74,11 +75,25 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { inviteExists = true } // check if user approval is already pending - if !inviteExists && logic.IsPendingUser(content.Login) { + if !inviteExists && logic.IsPendingUser(content.Email) { handleOauthUserSignUpApprovalPending(w) return } - _, err = logic.GetUser(content.Login) + // if user exists with provider ID, convert them into email ID + user, err := logic.GetUser(content.Login) + if err == nil { + // checks if user exists with email + _, err := logic.GetUser(content.Email) + if err != nil { + user.UserName = content.Email + user.ExternalProviderID = content.Login + database.DeleteRecord(database.USERS_TABLE_NAME, content.Login) + d, _ := json.Marshal(user) + database.Insert(user.UserName, string(d), database.USERS_TABLE_NAME) + } + + } + _, err = logic.GetUser(content.Email) if err != nil { if database.IsEmptyRecord(err) { // user must not exist, so try to make one if inviteExists { @@ -88,20 +103,20 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - user.UserName = content.Login // overrides email with github id + user.ExternalProviderID = content.Login if err = logic.CreateUser(&user); err != nil { handleSomethingWentWrong(w) return } logic.DeleteUserInvite(content.Email) - logic.DeletePendingUser(content.Login) + logic.DeletePendingUser(content.Email) } else { - if !isEmailAllowed(content.Login) { + if !isEmailAllowed(content.Email) { handleOauthUserNotAllowedToSignUp(w) return } err = logic.InsertPendingUser(&models.User{ - UserName: content.Login, + UserName: content.Email, }) if err != nil { handleSomethingWentWrong(w) @@ -115,7 +130,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { return } } - user, err := logic.GetUser(content.Login) + user, err = logic.GetUser(content.Email) if err != nil { handleOauthUserNotFound(w) return @@ -135,7 +150,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { } // send a netmaker jwt token var authRequest = models.UserAuthParams{ - UserName: content.Login, + UserName: content.Email, Password: newPass, } @@ -145,11 +160,11 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { return } - logger.Log(1, "completed github OAuth sigin in for", content.Login) - http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?login="+jwt+"&user="+content.Login, http.StatusPermanentRedirect) + logger.Log(1, "completed github OAuth sigin in for", content.Email) + http.Redirect(w, r, servercfg.GetFrontendURL()+"/login?login="+jwt+"&user="+content.Email, http.StatusPermanentRedirect) } -func getGithubUserInfo(state string, code string) (*OAuthUser, error) { +func getGithubUserInfo(state, code string) (*OAuthUser, error) { oauth_state_string, isValid := logic.IsStateValid(state) if (!isValid || state != oauth_state_string) && !isStateCached(state) { return nil, fmt.Errorf("invalid oauth state") @@ -187,7 +202,16 @@ func getGithubUserInfo(state string, code string) (*OAuthUser, error) { } userInfo.AccessToken = string(data) if userInfo.Email == "" { - userInfo.Email = getUserEmailFromClaims(token.AccessToken) + // if user's email is not made public, get the info from the github emails api + logger.Log(0, "=======> fetching user email from github api") + userInfo.Email, err = getGithubEmailsInfo(token.AccessToken) + if err != nil { + logger.Log(0, "failed to fetch user's email from github: ", err.Error()) + } + } + if userInfo.Email == "" { + err = errors.New("failed to fetch user email from SSO state") + return userInfo, err } return userInfo, nil } @@ -195,3 +219,37 @@ func getGithubUserInfo(state string, code string) (*OAuthUser, error) { func verifyGithubUser(token *oauth2.Token) bool { return token.Valid() } + +func getGithubEmailsInfo(accessToken string) (string, error) { + + var httpClient = &http.Client{} + var httpReq, reqErr = http.NewRequest("GET", "https://api.github.com/user/emails", nil) + if reqErr != nil { + return "", fmt.Errorf("failed to create request to GitHub") + } + httpReq.Header.Add("Accept", "application/vnd.github.v3+json") + httpReq.Header.Set("Authorization", "token "+accessToken) + response, err := httpClient.Do(httpReq) + if err != nil { + return "", fmt.Errorf("failed getting user info: %s", err.Error()) + } + defer response.Body.Close() + contents, err := io.ReadAll(response.Body) + if err != nil { + return "", fmt.Errorf("failed reading response body: %s", err.Error()) + } + + emailsInfo := []interface{}{} + err = json.Unmarshal(contents, &emailsInfo) + if err != nil { + return "", err + } + for _, info := range emailsInfo { + emailInfoMap := info.(map[string]interface{}) + if emailInfoMap["primary"].(bool) { + return emailInfoMap["email"].(string), nil + } + + } + return "", errors.New("email not found") +} From ed2a0a0a01e351d1fb8d722d2be6421385bffff3 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 2 Sep 2024 10:57:10 +0530 Subject: [PATCH 4/5] fix oidc invite flow --- models/user_mgmt.go | 22 +++++++++++----------- pro/auth/azure-ad.go | 4 ++-- pro/auth/github.go | 4 ++-- pro/auth/google.go | 2 +- pro/auth/oidc.go | 4 ++-- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/models/user_mgmt.go b/models/user_mgmt.go index a928f5282..a87a0f4b8 100644 --- a/models/user_mgmt.go +++ b/models/user_mgmt.go @@ -138,17 +138,17 @@ type UserGroup struct { // User struct - struct for Users type User struct { - UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"` - ExternalProviderID string `json:"external_provider_id"` - Password string `json:"password" bson:"password" validate:"required,min=5"` - IsAdmin bool `json:"isadmin" bson:"isadmin"` // deprecated - IsSuperAdmin bool `json:"issuperadmin"` // deprecated - RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` // deprecated - AuthType AuthType `json:"auth_type"` - UserGroups map[UserGroupID]struct{} `json:"user_group_ids"` - PlatformRoleID UserRoleID `json:"platform_role_id"` - NetworkRoles map[NetworkID]map[UserRoleID]struct{} `json:"network_roles"` - LastLoginTime time.Time `json:"last_login_time"` + UserName string `json:"username" bson:"username" validate:"min=3,max=40,in_charset|email"` + ExternalIdentityProviderID string `json:"external_identity_provider_id"` + Password string `json:"password" bson:"password" validate:"required,min=5"` + IsAdmin bool `json:"isadmin" bson:"isadmin"` // deprecated + IsSuperAdmin bool `json:"issuperadmin"` // deprecated + RemoteGwIDs map[string]struct{} `json:"remote_gw_ids"` // deprecated + AuthType AuthType `json:"auth_type"` + UserGroups map[UserGroupID]struct{} `json:"user_group_ids"` + PlatformRoleID UserRoleID `json:"platform_role_id"` + NetworkRoles map[NetworkID]map[UserRoleID]struct{} `json:"network_roles"` + LastLoginTime time.Time `json:"last_login_time"` } type ReturnUserWithRolesAndGroups struct { diff --git a/pro/auth/azure-ad.go b/pro/auth/azure-ad.go index fbe588ad2..7aa349536 100644 --- a/pro/auth/azure-ad.go +++ b/pro/auth/azure-ad.go @@ -85,7 +85,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { _, err := logic.GetUser(content.Email) if err != nil { user.UserName = content.Email - user.ExternalProviderID = content.UserPrincipalName + user.ExternalIdentityProviderID = content.UserPrincipalName database.DeleteRecord(database.USERS_TABLE_NAME, content.UserPrincipalName) d, _ := json.Marshal(user) database.Insert(user.UserName, string(d), database.USERS_TABLE_NAME) @@ -101,7 +101,7 @@ func handleAzureCallback(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - user.ExternalProviderID = content.UserPrincipalName + user.ExternalIdentityProviderID = content.UserPrincipalName if err = logic.CreateUser(&user); err != nil { handleSomethingWentWrong(w) return diff --git a/pro/auth/github.go b/pro/auth/github.go index 1cb52cf92..5d2db5941 100644 --- a/pro/auth/github.go +++ b/pro/auth/github.go @@ -86,7 +86,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { _, err := logic.GetUser(content.Email) if err != nil { user.UserName = content.Email - user.ExternalProviderID = content.Login + user.ExternalIdentityProviderID = content.Login database.DeleteRecord(database.USERS_TABLE_NAME, content.Login) d, _ := json.Marshal(user) database.Insert(user.UserName, string(d), database.USERS_TABLE_NAME) @@ -103,7 +103,7 @@ func handleGithubCallback(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - user.ExternalProviderID = content.Login + user.ExternalIdentityProviderID = content.Login if err = logic.CreateUser(&user); err != nil { handleSomethingWentWrong(w) return diff --git a/pro/auth/google.go b/pro/auth/google.go index 94db3a7c9..9ba9772c5 100644 --- a/pro/auth/google.go +++ b/pro/auth/google.go @@ -90,7 +90,7 @@ func handleGoogleCallback(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } - + user.ExternalIdentityProviderID = content.Email if err = logic.CreateUser(&user); err != nil { handleSomethingWentWrong(w) return diff --git a/pro/auth/oidc.go b/pro/auth/oidc.go index 72dc2b957..2fc71f665 100644 --- a/pro/auth/oidc.go +++ b/pro/auth/oidc.go @@ -80,10 +80,9 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) { handleOauthNotConfigured(w) return } - var inviteExists bool // check if invite exists for User - in, err := logic.GetUserInvite(content.Login) + in, err := logic.GetUserInvite(content.Email) if err == nil { inviteExists = true } @@ -102,6 +101,7 @@ func handleOIDCCallback(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } + user.ExternalIdentityProviderID = content.Email if err = logic.CreateUser(&user); err != nil { handleSomethingWentWrong(w) return From c4bfae77df6f4f861f989d768c3d70ac528d4a63 Mon Sep 17 00:00:00 2001 From: abhishek9686 Date: Mon, 2 Sep 2024 14:15:04 +0530 Subject: [PATCH 5/5] increase log verbose --- pro/auth/github.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pro/auth/github.go b/pro/auth/github.go index 5d2db5941..e72dc439d 100644 --- a/pro/auth/github.go +++ b/pro/auth/github.go @@ -203,7 +203,7 @@ func getGithubUserInfo(state, code string) (*OAuthUser, error) { userInfo.AccessToken = string(data) if userInfo.Email == "" { // if user's email is not made public, get the info from the github emails api - logger.Log(0, "=======> fetching user email from github api") + logger.Log(2, "fetching user email from github api") userInfo.Email, err = getGithubEmailsInfo(token.AccessToken) if err != nil { logger.Log(0, "failed to fetch user's email from github: ", err.Error())