feat: adds projectID prop in IAM user account

Closes #1621

These changes introduce the `projectID` field in IAM user accounts. The field has been added across all IAM systems: internal, IPA, LDAP, Vault, and S3 object. Support has also been added to the admin CLI commands to create, update, and list users with the `projectID` included.
This commit is contained in:
niksis02
2025-11-07 20:33:14 +04:00
parent 743cb03808
commit a64733bfbe
8 changed files with 125 additions and 53 deletions

View File

@@ -50,6 +50,7 @@ type Account struct {
Role Role `json:"role"`
UserID int `json:"userID"`
GroupID int `json:"groupID"`
ProjectID int `json:"projectID"`
}
type ListUserAccountsResult struct {
@@ -62,6 +63,7 @@ type MutableProps struct {
Role Role `json:"role"`
UserID *int `json:"userID"`
GroupID *int `json:"groupID"`
ProjectID *int `json:"projectID"`
}
func (m MutableProps) Validate() error {
@@ -82,6 +84,9 @@ func updateAcc(acc *Account, props MutableProps) {
if props.UserID != nil {
acc.UserID = *props.UserID
}
if props.ProjectID != nil {
acc.ProjectID = *props.ProjectID
}
if props.Role != "" {
acc.Role = props.Role
}
@@ -119,6 +124,7 @@ type Opts struct {
LDAPRoleAtr string
LDAPUserIdAtr string
LDAPGroupIdAtr string
LDAPProjectIdAtr string
LDAPTLSSkipVerify bool
VaultEndpointURL string
VaultNamespace string
@@ -160,7 +166,7 @@ func New(o *Opts) (IAMService, error) {
case o.LDAPServerURL != "":
svc, err = NewLDAPService(o.RootAccount, o.LDAPServerURL, o.LDAPBindDN, o.LDAPPassword,
o.LDAPQueryBase, o.LDAPAccessAtr, o.LDAPSecretAtr, o.LDAPRoleAtr, o.LDAPUserIdAtr,
o.LDAPGroupIdAtr, o.LDAPObjClasses, o.LDAPTLSSkipVerify)
o.LDAPGroupIdAtr, o.LDAPProjectIdAtr, o.LDAPObjClasses, o.LDAPTLSSkipVerify)
fmt.Printf("initializing LDAP IAM with %q\n", o.LDAPServerURL)
case o.S3Endpoint != "":
svc, err = NewS3(o.RootAccount, o.S3Access, o.S3Secret, o.S3Region, o.S3Bucket,

View File

@@ -199,6 +199,7 @@ func (s *IAMServiceInternal) ListUserAccounts() ([]Account, error) {
Role: conf.AccessAccounts[k].Role,
UserID: conf.AccessAccounts[k].UserID,
GroupID: conf.AccessAccounts[k].GroupID,
ProjectID: conf.AccessAccounts[k].ProjectID,
})
}

View File

@@ -132,6 +132,7 @@ func (ipa *IpaIAMService) GetUserAccount(access string) (Account, error) {
userResult := struct {
Gidnumber []string
Uidnumber []string
PidNumber []string
}{}
err = ipa.rpc(req, &userResult)
@@ -147,12 +148,17 @@ func (ipa *IpaIAMService) GetUserAccount(access string) (Account, error) {
if err != nil {
return Account{}, fmt.Errorf("ipa gid invalid: %w", err)
}
pId, err := strconv.Atoi(userResult.PidNumber[0])
if err != nil {
return Account{}, fmt.Errorf("ipa pid invalid: %w", err)
}
account := Account{
Access: access,
Role: RoleUser,
UserID: uid,
GroupID: gid,
ProjectID: pId,
}
session_key := make([]byte, 16)

View File

@@ -36,6 +36,7 @@ type LdapIAMService struct {
roleAtr string
groupIdAtr string
userIdAtr string
projectIdAtr string
rootAcc Account
url string
bindDN string
@@ -46,9 +47,9 @@ type LdapIAMService struct {
var _ IAMService = &LdapIAMService{}
func NewLDAPService(rootAcc Account, ldapURL, bindDN, pass, queryBase, accAtr, secAtr, roleAtr, userIdAtr, groupIdAtr, objClasses string, tlsSkipVerify bool) (IAMService, error) {
func NewLDAPService(rootAcc Account, ldapURL, bindDN, pass, queryBase, accAtr, secAtr, roleAtr, userIdAtr, groupIdAtr, projectIdAtr, objClasses string, tlsSkipVerify bool) (IAMService, error) {
if ldapURL == "" || bindDN == "" || pass == "" || queryBase == "" || accAtr == "" ||
secAtr == "" || roleAtr == "" || userIdAtr == "" || groupIdAtr == "" || objClasses == "" {
secAtr == "" || roleAtr == "" || userIdAtr == "" || groupIdAtr == "" || projectIdAtr == "" || objClasses == "" {
return nil, fmt.Errorf("required parameters list not fully provided")
}
@@ -71,6 +72,7 @@ func NewLDAPService(rootAcc Account, ldapURL, bindDN, pass, queryBase, accAtr, s
roleAtr: roleAtr,
userIdAtr: userIdAtr,
groupIdAtr: groupIdAtr,
projectIdAtr: projectIdAtr,
rootAcc: rootAcc,
url: ldapURL,
bindDN: bindDN,
@@ -142,6 +144,7 @@ func (ld *LdapIAMService) CreateAccount(account Account) error {
userEntry.Attribute(ld.roleAtr, []string{string(account.Role)})
userEntry.Attribute(ld.groupIdAtr, []string{fmt.Sprint(account.GroupID)})
userEntry.Attribute(ld.userIdAtr, []string{fmt.Sprint(account.UserID)})
userEntry.Attribute(ld.projectIdAtr, []string{fmt.Sprint(account.ProjectID)})
err := ld.execute(func(c *ldap.Conn) error {
return c.Add(userEntry)
@@ -177,7 +180,7 @@ func (ld *LdapIAMService) GetUserAccount(access string) (Account, error) {
0,
false,
ld.buildSearchFilter(access),
[]string{ld.accessAtr, ld.secretAtr, ld.roleAtr, ld.userIdAtr, ld.groupIdAtr},
[]string{ld.accessAtr, ld.secretAtr, ld.roleAtr, ld.userIdAtr, ld.groupIdAtr, ld.projectIdAtr},
nil,
)
@@ -216,12 +219,19 @@ func (ld *LdapIAMService) GetUserAccount(access string) (Account, error) {
return Account{}, fmt.Errorf("invalid entry value for user-id %q: %w",
entry.GetAttributeValue(ld.userIdAtr), err)
}
projectID, err := strconv.Atoi(entry.GetAttributeValue(ld.projectIdAtr))
if err != nil {
return Account{}, fmt.Errorf("invalid entry value for project-id %q: %w",
entry.GetAttributeValue(ld.projectIdAtr), err)
}
return Account{
Access: entry.GetAttributeValue(ld.accessAtr),
Secret: entry.GetAttributeValue(ld.secretAtr),
Role: Role(entry.GetAttributeValue(ld.roleAtr)),
GroupID: groupId,
UserID: userId,
ProjectID: projectID,
}, nil
}
@@ -236,6 +246,9 @@ func (ld *LdapIAMService) UpdateUserAccount(access string, props MutableProps) e
if props.UserID != nil {
req.Replace(ld.userIdAtr, []string{fmt.Sprint(*props.UserID)})
}
if props.ProjectID != nil {
req.Replace(ld.projectIdAtr, []string{fmt.Sprint(*props.ProjectID)})
}
if props.Role != "" {
req.Replace(ld.roleAtr, []string{string(props.Role)})
}
@@ -273,7 +286,7 @@ func (ld *LdapIAMService) ListUserAccounts() ([]Account, error) {
0,
false,
ld.buildSearchFilter(""),
[]string{ld.accessAtr, ld.secretAtr, ld.roleAtr, ld.groupIdAtr, ld.userIdAtr},
[]string{ld.accessAtr, ld.secretAtr, ld.roleAtr, ld.groupIdAtr, ld.projectIdAtr, ld.userIdAtr},
nil,
)
@@ -298,11 +311,18 @@ func (ld *LdapIAMService) ListUserAccounts() ([]Account, error) {
return nil, fmt.Errorf("invalid entry value for user-id %q: %w",
el.GetAttributeValue(ld.userIdAtr), err)
}
projectID, err := strconv.Atoi(el.GetAttributeValue(ld.projectIdAtr))
if err != nil {
return nil, fmt.Errorf("invalid entry value for project-id %q: %w",
el.GetAttributeValue(ld.groupIdAtr), err)
}
result = append(result, Account{
Access: el.GetAttributeValue(ld.accessAtr),
Secret: el.GetAttributeValue(ld.secretAtr),
Role: Role(el.GetAttributeValue(ld.roleAtr)),
GroupID: groupId,
ProjectID: projectID,
UserID: userId,
})
}

View File

@@ -210,6 +210,7 @@ func (s *IAMServiceS3) ListUserAccounts() ([]Account, error) {
Role: conf.AccessAccounts[k].Role,
UserID: conf.AccessAccounts[k].UserID,
GroupID: conf.AccessAccounts[k].GroupID,
ProjectID: conf.AccessAccounts[k].ProjectID,
})
}

View File

@@ -369,6 +369,14 @@ func parseVaultUserAccount(data map[string]any, access string) (acc Account, err
if err != nil {
return acc, errInvalidUser
}
projectIdJson, ok := usrAcc["projectID"].(json.Number)
if !ok {
return acc, errInvalidUser
}
projectID, err := projectIdJson.Int64()
if err != nil {
return acc, errInvalidUser
}
return Account{
Access: acss,
@@ -376,5 +384,6 @@ func parseVaultUserAccount(data map[string]any, access string) (acc Account, err
Role: Role(role),
UserID: int(userId),
GroupID: int(groupId),
ProjectID: int(projectID),
}, nil
}

View File

@@ -82,6 +82,11 @@ func adminCommand() *cli.Command {
Usage: "groupID for the new user",
Aliases: []string{"gi"},
},
&cli.IntFlag{
Name: "project-id",
Usage: "projectID for the new user",
Aliases: []string{"pi"},
},
},
},
{
@@ -115,6 +120,11 @@ func adminCommand() *cli.Command {
Usage: "groupID for the new user",
Aliases: []string{"gi"},
},
&cli.IntFlag{
Name: "project-id",
Usage: "projectID for the new user",
Aliases: []string{"pi"},
},
},
},
{
@@ -214,7 +224,7 @@ func initHTTPClient() *http.Client {
func createUser(ctx *cli.Context) error {
access, secret, role := ctx.String("access"), ctx.String("secret"), ctx.String("role")
userID, groupID := ctx.Int("user-id"), ctx.Int("group-id")
userID, groupID, projectID := ctx.Int("user-id"), ctx.Int("group-id"), ctx.Int("project-id")
if access == "" || secret == "" {
return fmt.Errorf("invalid input parameters for the new user access/secret keys")
}
@@ -228,6 +238,7 @@ func createUser(ctx *cli.Context) error {
Role: auth.Role(role),
UserID: userID,
GroupID: groupID,
ProjectID: projectID,
}
accxml, err := xml.Marshal(acc)
@@ -316,7 +327,14 @@ func deleteUser(ctx *cli.Context) error {
}
func updateUser(ctx *cli.Context) error {
access, secret, userId, groupId, role := ctx.String("access"), ctx.String("secret"), ctx.Int("user-id"), ctx.Int("group-id"), auth.Role(ctx.String("role"))
access, secret, userId, groupId, projectID, role :=
ctx.String("access"),
ctx.String("secret"),
ctx.Int("user-id"),
ctx.Int("group-id"),
ctx.Int("projectID"),
auth.Role(ctx.String("role"))
props := auth.MutableProps{}
if ctx.IsSet("role") {
if !role.IsValid() {
@@ -333,6 +351,9 @@ func updateUser(ctx *cli.Context) error {
if ctx.IsSet("group-id") {
props.GroupID = &groupId
}
if ctx.IsSet("project-id") {
props.ProjectID = &projectID
}
propsxml, err := xml.Marshal(props)
if err != nil {
@@ -433,10 +454,10 @@ const (
func printAcctTable(accs []auth.Account) {
w := new(tabwriter.Writer)
w.Init(os.Stdout, minwidth, tabwidth, padding, padchar, flags)
fmt.Fprintln(w, "Account\tRole\tUserID\tGroupID")
fmt.Fprintln(w, "-------\t----\t------\t-------")
fmt.Fprintln(w, "Account\tRole\tUserID\tGroupID\tProjectID")
fmt.Fprintln(w, "-------\t----\t------\t-------\t---------")
for _, acc := range accs {
fmt.Fprintf(w, "%v\t%v\t%v\t%v\n", acc.Access, acc.Role, acc.UserID, acc.GroupID)
fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\n", acc.Access, acc.Role, acc.UserID, acc.GroupID, acc.ProjectID)
}
fmt.Fprintln(w)
w.Flush()

View File

@@ -65,6 +65,7 @@ var (
ldapQueryBase, ldapObjClasses string
ldapAccessAtr, ldapSecAtr, ldapRoleAtr string
ldapUserIdAtr, ldapGroupIdAtr string
ldapProjectIdAtr string
ldapTLSSkipVerify bool
vaultEndpointURL, vaultNamespace string
vaultSecretStoragePath string
@@ -405,6 +406,12 @@ func initFlags() []cli.Flag {
EnvVars: []string{"VGW_IAM_LDAP_GROUP_ID_ATR"},
Destination: &ldapGroupIdAtr,
},
&cli.StringFlag{
Name: "iam-ldap-project-id-atr",
Usage: "ldap server user project id attribute name",
EnvVars: []string{"VGW_IAM_LDAP_PROJECT_ID_ATR"},
Destination: &ldapProjectIdAtr,
},
&cli.BoolFlag{
Name: "iam-ldap-tls-skip-verify",
Usage: "disable TLS certificate verification for LDAP connections (insecure, for self-signed certificates)",
@@ -699,6 +706,7 @@ func runGateway(ctx context.Context, be backend.Backend) error {
LDAPRoleAtr: ldapRoleAtr,
LDAPUserIdAtr: ldapUserIdAtr,
LDAPGroupIdAtr: ldapGroupIdAtr,
LDAPProjectIdAtr: ldapProjectIdAtr,
LDAPTLSSkipVerify: ldapTLSSkipVerify,
VaultEndpointURL: vaultEndpointURL,
VaultNamespace: vaultNamespace,