From 8f7a1bfc8649435011379a6acf572b393c1492c8 Mon Sep 17 00:00:00 2001 From: jonaustin09 Date: Tue, 3 Oct 2023 14:02:21 -0400 Subject: [PATCH] feat: Integrated a new option for IAM servcie: store IAM data in LDAP server --- auth/iam-ldap.go | 128 ++++++++++++++++++++++++++++++++++++++++++ auth/iam.go | 24 ++++++++ cmd/versitygw/main.go | 92 ++++++++++++++++++++++-------- go.mod | 5 +- go.sum | 19 +++++++ 5 files changed, 243 insertions(+), 25 deletions(-) create mode 100644 auth/iam-ldap.go diff --git a/auth/iam-ldap.go b/auth/iam-ldap.go new file mode 100644 index 0000000..38628a6 --- /dev/null +++ b/auth/iam-ldap.go @@ -0,0 +1,128 @@ +package auth + +import ( + "fmt" + "strings" + + "github.com/go-ldap/ldap/v3" +) + +type LdapIAMService struct { + conn *ldap.Conn + queryBase string + objClasses []string + accessAtr string + secretAtr string + roleAtr string +} + +var _ IAMService = &LdapIAMService{} + +func NewLDAPService(url, bindDN, pass, queryBase, accAtr, secAtr, roleAtr, objClasses string) (IAMService, error) { + if url == "" || bindDN == "" || pass == "" || queryBase == "" || accAtr == "" || secAtr == "" || roleAtr == "" || objClasses == "" { + return nil, fmt.Errorf("required parameters list not fully provided") + } + conn, err := ldap.Dial("tcp", url) + if err != nil { + return nil, fmt.Errorf("failed to connect to LDAP server: %w", err) + } + + err = conn.Bind(bindDN, pass) + if err != nil { + return nil, fmt.Errorf("failed to bind to LDAP server %w", err) + } + return &LdapIAMService{ + conn: conn, + queryBase: queryBase, + objClasses: strings.Split(objClasses, ","), + accessAtr: accAtr, + secretAtr: secAtr, + roleAtr: roleAtr, + }, nil +} + +func (ld *LdapIAMService) CreateAccount(account Account) error { + userEntry := ldap.NewAddRequest(fmt.Sprintf("%v=%v, %v", ld.accessAtr, account.Access, ld.queryBase), nil) + userEntry.Attribute("objectClass", ld.objClasses) + userEntry.Attribute(ld.accessAtr, []string{account.Access}) + userEntry.Attribute(ld.secretAtr, []string{account.Secret}) + userEntry.Attribute(ld.roleAtr, []string{account.Role}) + + err := ld.conn.Add(userEntry) + if err != nil { + return fmt.Errorf("error adding an entry: %w", err) + } + + return nil +} + +func (ld *LdapIAMService) GetUserAccount(access string) (Account, error) { + searchRequest := ldap.NewSearchRequest( + ld.queryBase, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, + 0, + false, + fmt.Sprintf("(%v=%v)", ld.accessAtr, access), + []string{ld.accessAtr, ld.secretAtr, ld.roleAtr}, + nil, + ) + + result, err := ld.conn.Search(searchRequest) + if err != nil { + return Account{}, err + } + + entry := result.Entries[0] + return Account{ + Access: entry.GetAttributeValue(ld.accessAtr), + Secret: entry.GetAttributeValue(ld.secretAtr), + Role: entry.GetAttributeValue(ld.roleAtr), + }, nil +} + +func (ld *LdapIAMService) DeleteUserAccount(access string) error { + delReq := ldap.NewDelRequest(fmt.Sprintf("%v=%v, %v", ld.accessAtr, access, ld.queryBase), nil) + + err := ld.conn.Del(delReq) + if err != nil { + return err + } + + return nil +} + +func (ld *LdapIAMService) ListUserAccounts() ([]Account, error) { + searchFilter := "" + for _, el := range ld.objClasses { + searchFilter += fmt.Sprintf("(objectClass=%v)", el) + } + searchRequest := ldap.NewSearchRequest( + ld.queryBase, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, + 0, + false, + fmt.Sprintf("(&%v)", searchFilter), + []string{ld.accessAtr, ld.secretAtr, ld.roleAtr}, + nil, + ) + + resp, err := ld.conn.Search(searchRequest) + if err != nil { + return nil, err + } + + result := []Account{} + for _, el := range resp.Entries { + result = append(result, Account{ + Access: el.GetAttributeValue(ld.accessAtr), + Secret: el.GetAttributeValue(ld.secretAtr), + Role: el.GetAttributeValue(ld.roleAtr), + }) + } + + return result, nil +} diff --git a/auth/iam.go b/auth/iam.go index 535649c..47bc3fc 100644 --- a/auth/iam.go +++ b/auth/iam.go @@ -36,3 +36,27 @@ type IAMService interface { } var ErrNoSuchUser = errors.New("user not found") + +type Opts struct { + Dir string + LDAPServerURL string + LDAPBindDN string + LDAPPassword string + LDAPQueryBase string + LDAPObjClasses string + LDAPAccessAtr string + LDAPSecretAtr string + LDAPRoleAtr string +} + +func New(o *Opts) (IAMService, error) { + if o.Dir == "" { + if o.LDAPServerURL == "" { + return IAMServiceSingle{}, nil + } else { + return NewLDAPService(o.LDAPServerURL, o.LDAPBindDN, o.LDAPPassword, o.LDAPQueryBase, o.LDAPAccessAtr, o.LDAPSecretAtr, o.LDAPRoleAtr, o.LDAPObjClasses) + } + } else { + return NewInternal(o.Dir) + } +} diff --git a/cmd/versitygw/main.go b/cmd/versitygw/main.go index 6cb6e2c..e23d0ec 100644 --- a/cmd/versitygw/main.go +++ b/cmd/versitygw/main.go @@ -33,18 +33,21 @@ import ( ) var ( - port, admPort string - rootUserAccess string - rootUserSecret string - region string - admCertFile, admKeyFile string - certFile, keyFile string - kafkaURL, kafkaTopic, kafkaKey string - natsURL, natsTopic string - logWebhookURL string - accessLog string - debug bool - iamDir string + port, admPort string + rootUserAccess string + rootUserSecret string + region string + admCertFile, admKeyFile string + certFile, keyFile string + kafkaURL, kafkaTopic, kafkaKey string + natsURL, natsTopic string + logWebhookURL string + accessLog string + debug bool + iamDir string + ldapURL, ldapBindDN, ldapPassword string + ldapQueryBase, ldapObjClasses string + ldapAccessAtr, ldapSecAtr, ldapRoleAtr string ) var ( @@ -213,6 +216,46 @@ func initFlags() []cli.Flag { Usage: "if defined, run internal iam service within this directory", Destination: &iamDir, }, + &cli.StringFlag{ + Name: "iam-ldap-url", + Usage: "ldap server url to store iam data", + Destination: &ldapURL, + }, + &cli.StringFlag{ + Name: "iam-ldap-bind-dn", + Usage: "ldap server binding dn, example: 'cn=admin,dc=example,dc=com'", + Destination: &ldapBindDN, + }, + &cli.StringFlag{ + Name: "iam-ldap-bind-pass", + Usage: "ldap server user password", + Destination: &ldapPassword, + }, + &cli.StringFlag{ + Name: "iam-ldap-query-base", + Usage: "ldap server destination query, example: 'ou=iam,dc=example,dc=com'", + Destination: &ldapQueryBase, + }, + &cli.StringFlag{ + Name: "iam-ldap-object-classes", + Usage: "ldap server object classes used to store the data. provide it as comma separated string, example: 'top,person'", + Destination: &ldapObjClasses, + }, + &cli.StringFlag{ + Name: "iam-ldap-access-atr", + Usage: "ldap server user access key id attribute name", + Destination: &ldapAccessAtr, + }, + &cli.StringFlag{ + Name: "iam-ldap-secret-atr", + Usage: "ldap server user secret access key attribute name", + Destination: &ldapSecAtr, + }, + &cli.StringFlag{ + Name: "iam-ldap-role-atr", + Usage: "ldap server user role attribute name", + Destination: &ldapRoleAtr, + }, } } @@ -275,18 +318,19 @@ func runGateway(ctx *cli.Context, be backend.Backend) error { admOpts = append(admOpts, s3api.WithAdminSrvTLS(cert)) } - var iam auth.IAMService - switch { - case iamDir != "": - var err error - iam, err = auth.NewInternal(iamDir) - if err != nil { - return fmt.Errorf("setup internal iam service: %w", err) - } - default: - // default gateway to single user mode when - // no other iam service configured - iam = auth.IAMServiceSingle{} + iam, err := auth.New(&auth.Opts{ + Dir: iamDir, + LDAPServerURL: ldapURL, + LDAPBindDN: ldapBindDN, + LDAPPassword: ldapPassword, + LDAPQueryBase: ldapQueryBase, + LDAPObjClasses: ldapObjClasses, + LDAPAccessAtr: ldapAccessAtr, + LDAPSecretAtr: ldapSecAtr, + LDAPRoleAtr: ldapRoleAtr, + }) + if err != nil { + return fmt.Errorf("setup iam: %w", err) } logger, err := s3log.InitLogger(&s3log.LogConfig{ diff --git a/go.mod b/go.mod index 3ae9142..5c4a95c 100644 --- a/go.mod +++ b/go.mod @@ -18,11 +18,14 @@ require ( ) require ( + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.11 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.3.43 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.15.0 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.1 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.23.0 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect + github.com/go-ldap/ldap/v3 v3.4.6 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/nats-io/nats-server/v2 v2.9.20 // indirect @@ -30,7 +33,7 @@ require ( github.com/nats-io/nuid v1.0.1 // indirect github.com/pierrec/lz4/v4 v4.1.18 // indirect github.com/stretchr/testify v1.8.1 // indirect - golang.org/x/crypto v0.11.0 // indirect + golang.org/x/crypto v0.13.0 // indirect google.golang.org/protobuf v1.31.0 // indirect ) diff --git a/go.sum b/go.sum index a46bb8d..c331b6b 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,6 @@ +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/aws/aws-sdk-go-v2 v1.21.0 h1:gMT0IW+03wtYJhRqTVYn0wLzwdnK9sRMcxmtfGzRdJc= @@ -43,6 +46,10 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA= +github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A= +github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc= github.com/gofiber/fiber/v2 v2.49.2 h1:ONEN3/Vc+dUCxxDgZZwpqvhISgHqb+bu+isBiEyKEQs= github.com/gofiber/fiber/v2 v2.49.2/go.mod h1:gNsKnyrmfEWFpJxQAV0qvW6l70K1dZGno12oLtukcts= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -121,14 +128,20 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -138,21 +151,27 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=