Skip to content

Commit

Permalink
authn: Refactor User entity and remove cluster ID from LoginMsg
Browse files Browse the repository at this point in the history
- Removed roles string[] from the User entity to simplify role management.
- Removed cluster ID from the login message to streamline the login process.

Signed-off-by: Abhishek Gaikwad <[email protected]>
  • Loading branch information
gaikwadabhishek committed Jul 5, 2024
1 parent ccef808 commit 658058e
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 135 deletions.
14 changes: 7 additions & 7 deletions api/authn/authn.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Package authn provides AuthN API over HTTP(S)
/*
* Copyright (c) 2018-2023, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
*/
package authn

Expand Down Expand Up @@ -61,9 +61,9 @@ func DeleteUser(bp api.BaseParams, userID string) error {
// Authorize a user and return a user token in case of success.
// The token expires in `expire` time. If `expire` is `nil` the expiration
// time is set by AuthN (default AuthN expiration time is 24 hours)
func LoginUser(bp api.BaseParams, userID, pass, clusterID string, expire *time.Duration) (token *TokenMsg, err error) {
func LoginUser(bp api.BaseParams, userID, pass string, expire *time.Duration) (token *TokenMsg, err error) {
bp.Method = http.MethodPost
rec := LoginMsg{Password: pass, ExpiresIn: expire, ClusterID: clusterID}
rec := LoginMsg{Password: pass, ExpiresIn: expire}
reqParams := api.AllocRp()
defer api.FreeRp(reqParams)
{
Expand Down Expand Up @@ -135,8 +135,8 @@ func GetRegisteredClusters(bp api.BaseParams, spec CluACL) ([]*CluACL, error) {

clusters := &RegisteredClusters{}
_, err := reqParams.DoReqAny(clusters)
rec := make([]*CluACL, 0, len(clusters.M))
for _, clu := range clusters.M {
rec := make([]*CluACL, 0, len(clusters.Clusters))
for _, clu := range clusters.Clusters {
rec = append(rec, clu)
}
less := func(i, j int) bool { return rec[i].ID < rec[j].ID }
Expand Down Expand Up @@ -174,7 +174,7 @@ func GetAllRoles(bp api.BaseParams) ([]*Role, error) {
roles := make([]*Role, 0)
_, err := reqParams.DoReqAny(&roles)

less := func(i, j int) bool { return roles[i].ID < roles[j].ID }
less := func(i, j int) bool { return roles[i].Name < roles[j].Name }
sort.Slice(roles, less)
return roles, err
}
Expand Down Expand Up @@ -240,7 +240,7 @@ func UpdateRole(bp api.BaseParams, roleSpec *Role) error {
defer api.FreeRp(reqParams)
{
reqParams.BaseParams = bp
reqParams.Path = apc.URLPathRoles.Join(roleSpec.ID)
reqParams.Path = apc.URLPathRoles.Join(roleSpec.Name)
reqParams.Body = msg
reqParams.Header = http.Header{cos.HdrContentType: []string{cos.ContentJSON}}
}
Expand Down
32 changes: 17 additions & 15 deletions api/authn/entity.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Package authn provides AuthN API over HTTP(S)
/*
* Copyright (c) 2018-2022, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
*/
package authn

Expand All @@ -18,37 +18,39 @@ const (

type (
User struct {
ID string `json:"id"`
Password string `json:"pass,omitempty"`
Roles []string `json:"roles"`
ClusterACLs []*CluACL `json:"clusters"`
BucketACLs []*BckACL `json:"buckets"` // list of buckets with special permissions
ID string `json:"id"`
Password string `json:"pass,omitempty"`
Roles []*Role `json:"roles"`
}

CluACL struct {
ID string `json:"id"`
Alias string `json:"alias,omitempty"`
Access apc.AccessAttrs `json:"perm,string,omitempty"`
URLs []string `json:"urls,omitempty"`
}

BckACL struct {
Bck cmn.Bck `json:"bck"`
Access apc.AccessAttrs `json:"perm,string"`
}

TokenMsg struct {
Token string `json:"token"`
}

LoginMsg struct {
Password string `json:"password"`
ExpiresIn *time.Duration `json:"expires_in"`
ClusterID string `json:"cluster_id"`
}

RegisteredClusters struct {
M map[string]*CluACL `json:"clusters,omitempty"`
Clusters map[string]*CluACL `json:"clusters,omitempty"`
}

Role struct {
ID string `json:"name"`
Desc string `json:"desc"`
Roles []string `json:"roles"`
Name string `json:"name"`
Description string `json:"desc"`
ClusterACLs []*CluACL `json:"clusters"`
BucketACLs []*BckACL `json:"buckets"`
IsAdmin bool `json:"admin"`
Expand All @@ -60,10 +62,10 @@ type (
//////////

// IsAdmin returns true if the user is an admin or super-user,
// i.e. the user has the full access to everything.
func (uInfo *User) IsAdmin() bool {
for _, r := range uInfo.Roles {
if r == AdminRole {
// i.e. the user has full access to everything.
func (u *User) IsAdmin() bool {
for _, r := range u.Roles {
if r.Name == AdminRole {
return true
}
}
Expand Down
40 changes: 21 additions & 19 deletions api/env/authn.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Package env contains environment variables
/*
* Copyright (c) 2018-2022, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
*/
package env

Expand All @@ -9,24 +9,26 @@ package env

var (
AuthN = struct {
Enabled string
URL string
TokenFile string
ConfDir string
LogDir string
LogLevel string
Port string
TTL string
UseHTTPS string
Enabled string
URL string
TokenFile string
ConfDir string
LogDir string
LogLevel string
Port string
TTL string
UseHTTPS string
AdminPassword string
}{
Enabled: "AIS_AUTHN_ENABLED",
URL: "AIS_AUTHN_URL",
TokenFile: "AIS_AUTHN_TOKEN_FILE", // fully qualified
ConfDir: "AIS_AUTHN_CONF_DIR", // contains AuthN config and tokens DB
LogDir: "AIS_AUTHN_LOG_DIR",
LogLevel: "AIS_AUTHN_LOG_LEVEL",
Port: "AIS_AUTHN_PORT",
TTL: "AIS_AUTHN_TTL",
UseHTTPS: "AIS_AUTHN_USE_HTTPS",
Enabled: "AIS_AUTHN_ENABLED",
URL: "AIS_AUTHN_URL",
TokenFile: "AIS_AUTHN_TOKEN_FILE", // fully qualified
ConfDir: "AIS_AUTHN_CONF_DIR", // contains AuthN config and tokens DB
LogDir: "AIS_AUTHN_LOG_DIR",
LogLevel: "AIS_AUTHN_LOG_LEVEL",
Port: "AIS_AUTHN_PORT",
TTL: "AIS_AUTHN_TTL",
UseHTTPS: "AIS_AUTHN_USE_HTTPS",
AdminPassword: "AIS_AUTHN_ADMIN_PASSWORD",
}
)
14 changes: 2 additions & 12 deletions cmd/authn/hserv.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,16 +249,6 @@ func (h *hserv) httpUserGet(w http.ResponseWriter, r *http.Request) {
return
}
uInfo.Password = ""
clus, err := h.mgr.clus()
if err != nil {
cmn.WriteErr(w, r, err)
return
}
for _, clu := range uInfo.ClusterACLs {
if cInfo, ok := clus[clu.ID]; ok {
clu.Alias = cInfo.Alias
}
}
writeJSON(w, uInfo, "user info")
}

Expand Down Expand Up @@ -412,15 +402,15 @@ func (h *hserv) httpSrvGet(w http.ResponseWriter, r *http.Request) {
return
}
cluList = &authn.RegisteredClusters{
M: map[string]*authn.CluACL{clu.ID: clu},
Clusters: map[string]*authn.CluACL{clu.ID: clu},
}
} else {
clus, err := h.mgr.clus()
if err != nil {
cmn.WriteErr(w, r, err, http.StatusInternalServerError)
return
}
cluList = &authn.RegisteredClusters{M: clus}
cluList = &authn.RegisteredClusters{Clusters: clus}
}
writeJSON(w, cluList, "auth")
}
Expand Down
86 changes: 34 additions & 52 deletions cmd/authn/mgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import (
"errors"
"fmt"
"net/http"
"os"
"time"

"github.com/NVIDIA/aistore/api/apc"
"github.com/NVIDIA/aistore/api/authn"
"github.com/NVIDIA/aistore/api/env"
"github.com/NVIDIA/aistore/cmd/authn/tok"
"github.com/NVIDIA/aistore/cmn"
"github.com/NVIDIA/aistore/cmn/cos"
Expand Down Expand Up @@ -100,9 +102,6 @@ func (m *mgr) updateUser(userID string, updateReq *authn.User) error {
if len(updateReq.Roles) != 0 {
uInfo.Roles = updateReq.Roles
}
uInfo.ClusterACLs = mergeClusterACLs(uInfo.ClusterACLs, updateReq.ClusterACLs, "")
uInfo.BucketACLs = mergeBckACLs(uInfo.BucketACLs, updateReq.BucketACLs, "")

return m.db.Set(usersCollection, userID, uInfo)
}

Expand All @@ -112,18 +111,6 @@ func (m *mgr) lookupUser(userID string) (*authn.User, error) {
if err != nil {
return nil, err
}

// update ACLs with roles's ones
for _, role := range uInfo.Roles {
rInfo := &authn.Role{}
err := m.db.Get(rolesCollection, role, rInfo)
if err != nil {
continue
}
uInfo.ClusterACLs = mergeClusterACLs(uInfo.ClusterACLs, rInfo.ClusterACLs, "")
uInfo.BucketACLs = mergeBckACLs(uInfo.BucketACLs, rInfo.BucketACLs, "")
}

return uInfo, nil
}

Expand All @@ -148,18 +135,18 @@ func (m *mgr) userList() (map[string]*authn.User, error) {

// Registers a new role
func (m *mgr) addRole(info *authn.Role) error {
if info.ID == "" {
if info.Name == "" {
return errors.New("role name is undefined")
}
if info.IsAdmin {
return fmt.Errorf("only built-in roles can have %q permissions", adminUserID)
}

_, err := m.db.GetString(rolesCollection, info.ID)
_, err := m.db.GetString(rolesCollection, info.Name)
if err == nil {
return fmt.Errorf("role %q already exists", info.ID)
return fmt.Errorf("role %q already exists", info.Name)
}
return m.db.Set(rolesCollection, info.ID, info)
return m.db.Set(rolesCollection, info.Name, info)
}

// Deletes an existing role
Expand All @@ -181,11 +168,8 @@ func (m *mgr) updateRole(role string, updateReq *authn.Role) error {
return cos.NewErrNotFound(m, "role "+role)
}

if updateReq.Desc != "" {
rInfo.Desc = updateReq.Desc
}
if len(updateReq.Roles) != 0 {
rInfo.Roles = updateReq.Roles
if updateReq.Description != "" {
rInfo.Description = updateReq.Description
}
rInfo.ClusterACLs = mergeClusterACLs(rInfo.ClusterACLs, updateReq.ClusterACLs, "")
rInfo.BucketACLs = mergeBckACLs(rInfo.BucketACLs, updateReq.BucketACLs, "")
Expand Down Expand Up @@ -229,12 +213,12 @@ func (m *mgr) createRolesForCluster(clu *authn.CluACL) {
if err := m.db.Get(rolesCollection, uid, rInfo); err == nil {
continue
}
rInfo.ID = uid
rInfo.Name = uid
cluName := clu.ID
if clu.Alias != "" {
cluName += "[" + clu.Alias + "]"
}
rInfo.Desc = fmt.Sprintf(pr.desc, cluName)
rInfo.Description = fmt.Sprintf(pr.desc, cluName)
rInfo.ClusterACLs = []*authn.CluACL{
{ID: clu.ID, Access: pr.perms},
}
Expand Down Expand Up @@ -372,6 +356,8 @@ func (m *mgr) issueToken(userID, pwd string, msg *authn.LoginMsg) (string, error
token string
uInfo = &authn.User{}
cid string
cluACLs []*authn.CluACL
bckACLs []*authn.BckACL
)

err = m.db.Get(usersCollection, userID, uInfo)
Expand All @@ -382,27 +368,11 @@ func (m *mgr) issueToken(userID, pwd string, msg *authn.LoginMsg) (string, error
if !isSamePassword(pwd, uInfo.Password) {
return "", errInvalidCredentials
}
if !uInfo.IsAdmin() {
if msg.ClusterID == "" {
return "", fmt.Errorf("Couldn't issue token for %q: cluster ID not set", userID)
}
cid = m.cluLookup(msg.ClusterID, msg.ClusterID)
if cid == "" {
return "", cos.NewErrNotFound(m, "cluster "+msg.ClusterID)
}
uInfo.ClusterACLs = mergeClusterACLs(make([]*authn.CluACL, 0, len(uInfo.ClusterACLs)), uInfo.ClusterACLs, cid)
uInfo.BucketACLs = mergeBckACLs(make([]*authn.BckACL, 0, len(uInfo.BucketACLs)), uInfo.BucketACLs, cid)
}

// update ACLs with roles's ones
for _, role := range uInfo.Roles {
rInfo := &authn.Role{}
err := m.db.Get(rolesCollection, role, rInfo)
if err != nil {
continue
}
uInfo.ClusterACLs = mergeClusterACLs(uInfo.ClusterACLs, rInfo.ClusterACLs, cid)
uInfo.BucketACLs = mergeBckACLs(uInfo.BucketACLs, rInfo.BucketACLs, cid)
cluACLs = mergeClusterACLs(cluACLs, role.ClusterACLs, cid)
bckACLs = mergeBckACLs(bckACLs, role.BucketACLs, cid)
}

// generate token
Expand All @@ -424,8 +394,8 @@ func (m *mgr) issueToken(userID, pwd string, msg *authn.LoginMsg) (string, error
if uInfo.IsAdmin() {
token, err = tok.IssueAdminJWT(expires, userID, Conf.Server.Secret)
} else {
m.fixClusterIDs(uInfo.ClusterACLs)
token, err = tok.IssueJWT(expires, userID, uInfo.BucketACLs, uInfo.ClusterACLs, Conf.Server.Secret)
m.fixClusterIDs(cluACLs)
token, err = tok.IssueJWT(expires, userID, bckACLs, cluACLs, Conf.Server.Secret)
}
return token, err
}
Expand Down Expand Up @@ -513,21 +483,33 @@ func isSamePassword(password, hashed string) bool {
func initializeDB(driver kvdb.Driver) error {
users, err := driver.List(usersCollection, "")
if err != nil || len(users) != 0 {
// return on erros or when DB is already initialized
// Return on errors or when DB is already initialized
return err
}

// Create the admin role
role := &authn.Role{
ID: authn.AdminRole,
Desc: "AuthN administrator",
IsAdmin: true,
Name: authn.AdminRole,
Description: "AuthN administrator",
IsAdmin: true,
}

if err := driver.Set(rolesCollection, authn.AdminRole, role); err != nil {
return err
}

// Get the admin password from the environment variable or use the default
password := os.Getenv(env.AuthN.AdminPassword)
if password == "" {
password = adminUserPass
}

// Create the admin user
su := &authn.User{
ID: adminUserID,
Password: encryptPassword(adminUserPass),
Roles: []string{authn.AdminRole},
Password: encryptPassword(password),
Roles: []*authn.Role{role},
}

return driver.Set(usersCollection, adminUserID, su)
}
Loading

0 comments on commit 658058e

Please sign in to comment.