diff --git a/auth/iam.go b/auth/iam.go index bd13d84..13f4751 100644 --- a/auth/iam.go +++ b/auth/iam.go @@ -45,11 +45,12 @@ func (r Role) IsValid() bool { // Account is a gateway IAM account type Account struct { - Access string `json:"access"` - Secret string `json:"secret"` - Role Role `json:"role"` - UserID int `json:"userID"` - GroupID int `json:"groupID"` + Access string `json:"access"` + Secret string `json:"secret"` + Role Role `json:"role"` + UserID int `json:"userID"` + GroupID int `json:"groupID"` + ProjectID int `json:"projectID"` } type ListUserAccountsResult struct { @@ -58,10 +59,11 @@ type ListUserAccountsResult struct { // Mutable props, which could be changed when updating an IAM account type MutableProps struct { - Secret *string `json:"secret"` - Role Role `json:"role"` - UserID *int `json:"userID"` - GroupID *int `json:"groupID"` + Secret *string `json:"secret"` + 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, diff --git a/auth/iam_internal.go b/auth/iam_internal.go index ed4910f..9843bc5 100644 --- a/auth/iam_internal.go +++ b/auth/iam_internal.go @@ -194,11 +194,12 @@ func (s *IAMServiceInternal) ListUserAccounts() ([]Account, error) { var accs []Account for _, k := range keys { accs = append(accs, Account{ - Access: k, - Secret: conf.AccessAccounts[k].Secret, - Role: conf.AccessAccounts[k].Role, - UserID: conf.AccessAccounts[k].UserID, - GroupID: conf.AccessAccounts[k].GroupID, + Access: k, + Secret: conf.AccessAccounts[k].Secret, + Role: conf.AccessAccounts[k].Role, + UserID: conf.AccessAccounts[k].UserID, + GroupID: conf.AccessAccounts[k].GroupID, + ProjectID: conf.AccessAccounts[k].ProjectID, }) } diff --git a/auth/iam_ipa.go b/auth/iam_ipa.go index 0fa8aab..b5a0437 100644 --- a/auth/iam_ipa.go +++ b/auth/iam_ipa.go @@ -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, + Access: access, + Role: RoleUser, + UserID: uid, + GroupID: gid, + ProjectID: pId, } session_key := make([]byte, 16) diff --git a/auth/iam_ldap.go b/auth/iam_ldap.go index 27cabc8..a51d0b4 100644 --- a/auth/iam_ldap.go +++ b/auth/iam_ldap.go @@ -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, + 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,12 +311,19 @@ 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, - UserID: userId, + Access: el.GetAttributeValue(ld.accessAtr), + Secret: el.GetAttributeValue(ld.secretAtr), + Role: Role(el.GetAttributeValue(ld.roleAtr)), + GroupID: groupId, + ProjectID: projectID, + UserID: userId, }) } diff --git a/auth/iam_s3_object.go b/auth/iam_s3_object.go index ba848be..2d152b7 100644 --- a/auth/iam_s3_object.go +++ b/auth/iam_s3_object.go @@ -205,11 +205,12 @@ func (s *IAMServiceS3) ListUserAccounts() ([]Account, error) { var accs []Account for _, k := range keys { accs = append(accs, Account{ - Access: k, - Secret: conf.AccessAccounts[k].Secret, - Role: conf.AccessAccounts[k].Role, - UserID: conf.AccessAccounts[k].UserID, - GroupID: conf.AccessAccounts[k].GroupID, + Access: k, + Secret: conf.AccessAccounts[k].Secret, + Role: conf.AccessAccounts[k].Role, + UserID: conf.AccessAccounts[k].UserID, + GroupID: conf.AccessAccounts[k].GroupID, + ProjectID: conf.AccessAccounts[k].ProjectID, }) } diff --git a/auth/iam_vault.go b/auth/iam_vault.go index 98c8149..6b3c8c0 100644 --- a/auth/iam_vault.go +++ b/auth/iam_vault.go @@ -369,12 +369,21 @@ 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, - Secret: secret, - Role: Role(role), - UserID: int(userId), - GroupID: int(groupId), + Access: acss, + Secret: secret, + Role: Role(role), + UserID: int(userId), + GroupID: int(groupId), + ProjectID: int(projectID), }, nil } diff --git a/cmd/versitygw/admin.go b/cmd/versitygw/admin.go index 612456d..dea5884 100644 --- a/cmd/versitygw/admin.go +++ b/cmd/versitygw/admin.go @@ -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") } @@ -223,11 +233,12 @@ func createUser(ctx *cli.Context) error { } acc := auth.Account{ - Access: access, - Secret: secret, - Role: auth.Role(role), - UserID: userID, - GroupID: groupID, + Access: access, + Secret: secret, + 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() diff --git a/cmd/versitygw/main.go b/cmd/versitygw/main.go index aa4f37a..74942a2 100644 --- a/cmd/versitygw/main.go +++ b/cmd/versitygw/main.go @@ -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,