-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement authentication and authorization (#217)
* implement authentication ang authorization Signed-off-by: Patricia Reinoso <[email protected]> * refactoring Signed-off-by: Patricia Reinoso <[email protected]> * fix circular dependency Signed-off-by: Patricia Reinoso <[email protected]> * fix auth db mistake Signed-off-by: Patricia Reinoso <[email protected]> * fix lint issues Signed-off-by: Patricia Reinoso <[email protected]> * use index on username for useraccount collection Signed-off-by: Patricia Reinoso <[email protected]> * fix login path to be compatible with ui Signed-off-by: Patricia Reinoso <[email protected]> * refactor tests and minor comments Signed-off-by: Patricia Reinoso <[email protected]> * renaming according to review comments Signed-off-by: Patricia Reinoso <[email protected]> * refactor user types according to the action Signed-off-by: Patricia Reinoso <[email protected]> * refactor to simplify authorization logic Signed-off-by: Patricia Reinoso <[email protected]> * add status endpoint Signed-off-by: Patricia Reinoso <[email protected]> * add account method to dbadapter and use it to know the number of user accounts created Signed-off-by: Patricia Reinoso <[email protected]> * refactoring: package reorganization Signed-off-by: Patricia Reinoso <[email protected]> * refactoring: reorganizing. moving login endpoint to auth Signed-off-by: Patricia Reinoso <[email protected]> * minor comments fixes Signed-off-by: Patricia Reinoso <[email protected]> * fix lint job Signed-off-by: Patricia Reinoso <[email protected]> * add readme information Signed-off-by: Patricia Reinoso <[email protected]> * fix frontend conflict on paths Signed-off-by: Patricia Reinoso <[email protected]> * fix method signature update Co-authored-by: Kayra <[email protected]> Signed-off-by: Patricia Reinoso <[email protected]> * fix use forbidden instead of unauthorized when the user does not have permission Signed-off-by: Patricia Reinoso <[email protected]> * add copyright and release version Signed-off-by: Patricia Reinoso <[email protected]> * fix copyright format Signed-off-by: Patricia Reinoso <[email protected]> * Update backend/auth/README.md Co-authored-by: gab-arrobo <[email protected]> Signed-off-by: Patricia Reinoso <[email protected]> * Update backend/auth/README.md Co-authored-by: gab-arrobo <[email protected]> Signed-off-by: Patricia Reinoso <[email protected]> * Update backend/auth/README.md Co-authored-by: gab-arrobo <[email protected]> Signed-off-by: Patricia Reinoso <[email protected]> * Update backend/auth/README.md Co-authored-by: gab-arrobo <[email protected]> Signed-off-by: Patricia Reinoso <[email protected]> * small fixes from review comments Signed-off-by: Patricia Reinoso <[email protected]> * add doc for swagger Signed-off-by: Patricia Reinoso <[email protected]> * add license Signed-off-by: Patricia Reinoso <[email protected]> * fix swagger doc Signed-off-by: Patricia Reinoso <[email protected]> * minor fixes from comments Signed-off-by: Patricia Reinoso <[email protected]> --------- Signed-off-by: Patricia Reinoso <[email protected]> Co-authored-by: Kayra <[email protected]> Co-authored-by: gab-arrobo <[email protected]>
- Loading branch information
1 parent
d8abc03
commit 4761989
Showing
32 changed files
with
3,367 additions
and
421 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,3 +29,4 @@ public/ | |
!/frontend/public/*.license | ||
.coverage/ | ||
vendor/ | ||
config/webuicfg.yaml |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
<!-- | ||
# SPDX-FileCopyrightText: 2021 Open Networking Foundation <[email protected]> | ||
# Copyright 2019 free5GC.org | ||
SPDX-FileCopyrightText: 2021 Open Networking Foundation <[email protected]> | ||
Copyright 2019 free5GC.org | ||
SPDX-FileCopyrightText: 2024 Canonical Ltd. | ||
SPDX-License-Identifier: Apache-2.0 | ||
--> | ||
[![Go Report Card](https://goreportcard.com/badge/github.com/omec-project/webconsole)](https://goreportcard.com/report/github.com/omec-project/webconsole) | ||
|
@@ -17,7 +17,7 @@ features Configuration Service provides APIs for subscriber management. | |
4. 5G clients can connect & get complete configuration copy through grpc interface. | ||
5. 4G clients communicate with Webconsole through REST interface | ||
|
||
# UI | ||
## UI | ||
|
||
Webconsole can optionally serve static files, which constitute the frontend part of the application. | ||
|
||
|
@@ -35,6 +35,12 @@ http://<webconsole-ip>:5000/ | |
|
||
An example static file has been placed in the `webconsole/ui/frontend_files` directory. | ||
|
||
## Authentication and Authorization | ||
|
||
The authentication and authorization feature ensures that only verified and authorized users can access the webui resources and interact with the system. | ||
|
||
This is an optional feature, disabled by default. For more details, refer to this [file](backend/auth/README.md). | ||
|
||
## Webconsole Architecture diagram | ||
|
||
![Architecture](/docs/images/architecture1.png) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
1.5.1-dev | ||
1.6.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
<!-- | ||
SPDX-License-Identifier: Apache-2.0 | ||
SPDX-FileCopyrightText: 2024 Canonical Ltd | ||
--> | ||
|
||
# Authentication and Authorization Feature | ||
|
||
Webui is the upstream component that offers an API to configure the 5G core network in Aether SD-Core. With the implementation of the Authentication and Authorization feature, security risks have been reduced, ensuring that access is restricted to authorized users only. | ||
|
||
This is an optional feature that is disabled by default. | ||
|
||
## The Feature | ||
|
||
JWT is used to authenticate users, ensuring secure access to the system. For protected endpoints, users must include a `token`, which Webui uses to verify their identity and grant access. | ||
|
||
Authorization is implemented based on these 2 roles: `AdminRole` and `UserRole`. The webui uses the `token` to determine the role of the user performing the action. | ||
|
||
Depending on their role, users have different levels of access to Webui operations: | ||
|
||
- `UserRole`: Users with this role can retrieve their own account information and change their own password. | ||
- `AdminRole`: Admin users have full access to all endpoints, allowing them to perform any action on their own account as well as on other users' accounts. | ||
|
||
Both `AdminRole` and `UserRole` users can manage additional resources, such as Network Slices, Device Groups, and Subscribers, but they must be logged in. | ||
|
||
The `AdminRole` user cannot be deleted. | ||
|
||
## Setup | ||
|
||
### Enable the Feature | ||
|
||
To enable this feature, add the following parameters to the config file. | ||
``` | ||
configuration: | ||
enableAuthentication: true | ||
mongodb: | ||
. . . | ||
webuiDbName: <name> | ||
webuiDbUrl: <url> | ||
``` | ||
|
||
### First User Creation | ||
|
||
On a fresh deployment, the endpoint for creating a new user is not protected, allowing initial setup without authentication: | ||
|
||
``` | ||
curl -v "localhost:5000/config/v1/account" \ | ||
--data '{ | ||
"username": <username>, | ||
"password": <password> | ||
}' | ||
``` | ||
|
||
The first user created will automatically be assigned the `AdminRole`. Only one user can hold the `AdminRole`, and this user cannot be deleted. | ||
|
||
For all subsequent user creations, authentication is required by logging in. | ||
|
||
## Endpoints that does not require authorization | ||
|
||
There are three endpoints that can be accessed without providing a JWT token in the request header: [Log in](#log-in), [Get Status](#get-status), [First User Creation](#first-user-creation) | ||
|
||
### Log in | ||
|
||
The log in operation is required before performing most actions on the Webui. It generates a `token`, which must be included in subsequent requests for authentication. | ||
|
||
``` | ||
curl -v "localhost:5000/login" \ | ||
--header 'Content-Type: application/json' \ | ||
--data '{ | ||
"username": <username>, | ||
"password": <password> | ||
}' | ||
``` | ||
Response: | ||
``` | ||
{"token":"eyJhbG123aad1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluVXNlciIsInBlcm1pc3Npb25zIjoxLCJleHAiOjE3MjY1ODIyNTZ9.YU6tveV3oXcfGMvqB7xIcP1Fs6c6ZZoP134Y8ozV4lA"} | ||
``` | ||
|
||
### Get Status | ||
This endpoint allows any user to check the status of the Webui without a `token`. The status indicates whether the system's initialization has occurred, specifically whether the first `AdminRole` user has been created, and if the system is ready to use. | ||
|
||
``` | ||
curl -v "localhost:5000/status" | ||
``` | ||
Response: | ||
``` | ||
{"initialized":false} | ||
``` | ||
or | ||
``` | ||
{"initialized":true} | ||
``` | ||
|
||
### First User Creation | ||
|
||
As mentioned above, the [First User Creation](#first-user-creation) endpoint does not require a `JWT token`, allowing initial setup without authentication. | ||
|
||
## User Management Endpoints | ||
|
||
To access any of the following endpoints, users must be logged in and include a valid JWT token in the request header. | ||
|
||
### Create User | ||
Create a new user by providing the username and password. | ||
``` | ||
curl -v -H "Authorization: Bearer <token>" "localhost:5000/config/v1/account" \ | ||
--data '{ | ||
"username": <username>, | ||
"password": <password> | ||
}' | ||
``` | ||
|
||
### Get Users | ||
Retrieve a list of all users. | ||
``` | ||
curl -v -H "Authorization: Bearer <token>" "localhost:5000/config/v1/account" | ||
``` | ||
Response: | ||
``` | ||
[{"username":"adminUser","role":1}] | ||
``` | ||
|
||
### Get User | ||
Retrieve details for a specific user by their username. | ||
``` | ||
curl -v -H "Authorization: Bearer <token>" "localhost:5000/config/v1/account/<username>" | ||
``` | ||
Response: | ||
``` | ||
{"username":"adminUser","role":1} | ||
``` | ||
|
||
### Change Password | ||
Change the password for a specific user. | ||
``` | ||
curl -v -H "Authorization: Bearer <token>" "localhost:5000/config/v1/account/<username>/change_password" \ | ||
--data '{ | ||
"password": <new_password> | ||
}' | ||
``` | ||
|
||
### Delete User | ||
Delete a specific user by their username. | ||
``` | ||
curl -v -H "Authorization: Bearer <token>" -X DELETE "localhost:5000/v1/config/account/<username>" | ||
``` | ||
|
||
## Other Endpoints | ||
|
||
Configuration endpoints now require the inclusion of a JWT token in the request header for authorization. | ||
``` | ||
curl -v -H "Authorization: Bearer <token>" "localhost:5000/api/subscriber/<imsi>" \ | ||
--header 'Content-Type: text/plain' \ | ||
--data '{ | ||
... | ||
}' | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// SPDX-FileCopyrightText: 2024 Canonical Ltd | ||
|
||
package auth | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/gin-gonic/gin" | ||
"github.com/golang-jwt/jwt" | ||
"github.com/omec-project/webconsole/backend/logger" | ||
"github.com/omec-project/webconsole/configmodels" | ||
"github.com/omec-project/webconsole/dbadapter" | ||
"go.mongodb.org/mongo-driver/bson" | ||
"golang.org/x/crypto/bcrypt" | ||
) | ||
|
||
const ( | ||
errorIncorrectCredentials = "incorrect username or password. Try again" | ||
errorInvalidDataProvided = "invalid data provided" | ||
errorLogin = "failed to log in" | ||
errorMissingPassword = "password is required" | ||
errorMissingUsername = "username is required" | ||
errorRetrieveUserAccount = "failed to retrieve user account" | ||
) | ||
|
||
type LoginParams struct { | ||
Username string `json:"username"` | ||
Password string `json:"password"` | ||
} | ||
|
||
type LoginResponse struct { | ||
Token string `json:"token"` | ||
} | ||
|
||
// LoginPost godoc | ||
// | ||
// @Description Log in. Only available if enableAuthentication is enabled. | ||
// @Tags Auth | ||
// @Param loginParams body LoginParams true " " | ||
// @Success 200 {object} LoginResponse "Authorization token" | ||
// @Failure 400 {object} nil "Bad request" | ||
// @Failure 401 {object} nil "Authentication failed" | ||
// @Failure 404 {object} nil "Page not found if enableAuthentication is disabled" | ||
// @Failure 500 {object} nil "Internal server error" | ||
// @Router /login [post] | ||
func Login(jwtSecret []byte) gin.HandlerFunc { | ||
return func(c *gin.Context) { | ||
var loginParams LoginParams | ||
err := c.ShouldBindJSON(&loginParams) | ||
if err != nil { | ||
logger.AuthLog.Errorln(err.Error()) | ||
c.JSON(http.StatusBadRequest, gin.H{"error": errorInvalidDataProvided}) | ||
return | ||
} | ||
if loginParams.Username == "" { | ||
c.JSON(http.StatusBadRequest, gin.H{"error": errorMissingUsername}) | ||
return | ||
} | ||
if loginParams.Password == "" { | ||
c.JSON(http.StatusBadRequest, gin.H{"error": errorMissingPassword}) | ||
return | ||
} | ||
|
||
filter := bson.M{"username": loginParams.Username} | ||
rawUserAccount, err := dbadapter.WebuiDBClient.RestfulAPIGetOne(configmodels.UserAccountDataColl, filter) | ||
if err != nil { | ||
c.JSON(http.StatusInternalServerError, gin.H{"error": errorRetrieveUserAccount}) | ||
return | ||
} | ||
if len(rawUserAccount) == 0 { | ||
c.JSON(http.StatusUnauthorized, gin.H{"error": errorIncorrectCredentials}) | ||
return | ||
} | ||
var dbUser configmodels.DBUserAccount | ||
err = json.Unmarshal(configmodels.MapToByte(rawUserAccount), &dbUser) | ||
if err != nil { | ||
c.JSON(http.StatusInternalServerError, gin.H{"error": errorRetrieveUserAccount}) | ||
return | ||
} | ||
if err = bcrypt.CompareHashAndPassword([]byte(dbUser.HashedPassword), []byte(loginParams.Password)); err != nil { | ||
logger.AuthLog.Errorln(err.Error()) | ||
c.JSON(http.StatusUnauthorized, gin.H{"error": errorIncorrectCredentials}) | ||
return | ||
} | ||
token, err := GenerateJWT(dbUser.Username, dbUser.Role, jwtSecret) | ||
if err != nil { | ||
logger.AuthLog.Errorln(err.Error()) | ||
c.JSON(http.StatusInternalServerError, gin.H{"error": errorLogin}) | ||
return | ||
} | ||
loginResponse := LoginResponse{ | ||
Token: token, | ||
} | ||
c.JSON(http.StatusOK, loginResponse) | ||
} | ||
} | ||
|
||
func expireAfter() int64 { | ||
return time.Now().Add(time.Hour * 1).Unix() | ||
} | ||
|
||
func GenerateJWT(username string, role int, jwtSecret []byte) (string, error) { | ||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtWebconsoleClaims{ | ||
Username: username, | ||
Role: role, | ||
StandardClaims: jwt.StandardClaims{ | ||
ExpiresAt: expireAfter(), | ||
}, | ||
}) | ||
tokenString, err := token.SignedString(jwtSecret) | ||
if err != nil { | ||
return "", err | ||
} | ||
return tokenString, nil | ||
} |
Oops, something went wrong.