Skip to content

Commit

Permalink
allow to enforce user requirements
Browse files Browse the repository at this point in the history
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
  • Loading branch information
drakkan committed Jun 4, 2024
1 parent e024aee commit e80408d
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 61 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ OPTIONS:
--secondary-group-prefix value Prefix for LDAP groups to map to secondary groups of SFTPGo users [$SFTPGO_PLUGIN_AUTH_SECONDARY_GROUP_PREFIX]
--membership-group-prefix value Prefix for LDAP groups to map to membership groups of SFTPGo users [$SFTPGO_PLUGIN_AUTH_MEMBERSHIP_GROUP_PREFIX]
--require-groups Require authenticated users to be members of at least one SFTPGo group (default: false) [$SFTPGO_PLUGIN_AUTH_REQUIRE_GROUPS]
--user-requirements value Requirements for SFTPGo users. 1 means users must be already defined in SFTPGo (default: 0) [$SFTPGO_PLUGIN_AUTH_USER_REQUIREMENTS]
--starttls value STARTTLS is the preferred method of encrypting an LDAP connection. Use it instead of using the ldaps:// URL schema (default: 0) [$SFTPGO_PLUGIN_AUTH_STARTTLS]
--users-base-dir value Users default base directory. Leave empty if already set in SFTPGo. If set it must be an absolute path [$SFTPGO_PLUGIN_AUTH_USERS_BASE_DIR]
--cache-time value Defines the cache time, in seconds, for authenticated users. 0 means no cache (default: 0) [$SFTPGO_PLUGIN_AUTH_CACHE_TIME]
Expand Down
28 changes: 15 additions & 13 deletions authenticator/authenticator.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ var (
func NewAuthenticator(dialURLs []string, baseDN, username, password string, startTLS int, skipTLSVerify bool,
baseDir string, cacheTime int, searchQuery string, groupAttributes, caCertificates []string,
primaryGroupPrefix, secondaryGroupPrefix, membershipGroupPrefix string, requiresGroup bool,
sftpgoUserRequirements int,
) (*LDAPAuthenticator, error) {
rootCAs, err := loadCACerts(caCertificates)
if err != nil {
Expand All @@ -45,19 +46,20 @@ func NewAuthenticator(dialURLs []string, baseDN, username, password string, star
InsecureSkipVerify: skipTLSVerify,
}
auth := &LDAPAuthenticator{
DialURLs: dialURLs,
BaseDN: baseDN,
Username: username,
Password: password,
StartTLS: startTLS,
SearchQuery: searchQuery,
GroupAttributes: groupAttributes,
BaseDir: baseDir,
PrimaryGroupPrefix: strings.ToLower(primaryGroupPrefix),
SecondaryGroupPrefix: strings.ToLower(secondaryGroupPrefix),
MembershipGroupPrefix: strings.ToLower(membershipGroupPrefix),
RequireGroups: requiresGroup,
tlsConfig: tlsConfig,
DialURLs: dialURLs,
BaseDN: baseDN,
Username: username,
Password: password,
StartTLS: startTLS,
SearchQuery: searchQuery,
GroupAttributes: groupAttributes,
BaseDir: baseDir,
PrimaryGroupPrefix: strings.ToLower(primaryGroupPrefix),
SecondaryGroupPrefix: strings.ToLower(secondaryGroupPrefix),
MembershipGroupPrefix: strings.ToLower(membershipGroupPrefix),
RequireGroups: requiresGroup,
SFTPGoUserRequirements: sftpgoUserRequirements,
tlsConfig: tlsConfig,
}
if err := auth.validate(); err != nil {
return nil, err
Expand Down
55 changes: 36 additions & 19 deletions authenticator/ldap.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,24 @@ import (
)

type LDAPAuthenticator struct {
DialURLs []string
BaseDN string
Username string
Password string
StartTLS int
SearchQuery string
GroupAttributes []string
PrimaryGroupPrefix string
SecondaryGroupPrefix string
MembershipGroupPrefix string
RequireGroups bool
BaseDir string
tlsConfig *tls.Config
monitorTicker *time.Ticker
cleanupDone chan bool
mu sync.RWMutex
activeURLs []string
DialURLs []string
BaseDN string
Username string
Password string
StartTLS int
SearchQuery string
GroupAttributes []string
PrimaryGroupPrefix string
SecondaryGroupPrefix string
MembershipGroupPrefix string
RequireGroups bool
SFTPGoUserRequirements int
BaseDir string
tlsConfig *tls.Config
monitorTicker *time.Ticker
cleanupDone chan bool
mu sync.RWMutex
activeURLs []string
}

func (a *LDAPAuthenticator) validate() error {
Expand Down Expand Up @@ -208,10 +209,14 @@ func (a *LDAPAuthenticator) CheckUserAndPass(username, password, _, _ string, us
if err := l.Bind(entry.DN, password); err != nil {
return nil, err
}
result, err := a.getUser(userAsJSON, entry.Attributes)
if err != nil {
return nil, err
}
if cache != nil {
cache.Add(username, password)
}
return a.getUser(userAsJSON, entry.Attributes)
return result, nil
}

func (a *LDAPAuthenticator) CheckUserAndTLSCert(_, _, _, _ string, _ []byte) ([]byte, error) {
Expand Down Expand Up @@ -327,6 +332,11 @@ func (a *LDAPAuthenticator) getUser(userAsJSON []byte, attributes []*ldap.EntryA
if err := json.Unmarshal(userAsJSON, &user); err != nil {
return nil, err
}
if user.ID == 0 && a.isSFTPGoUserRequired() {
err := errors.New("LDAP users not defined in SFTPGo are not allowed")
logger.AppLogger.Debug("no SFTPGo user defined", "username", user.Username, "err", err)
return nil, err
}
var groups []sdk.GroupMapping

for _, attr := range attributes {
Expand Down Expand Up @@ -358,7 +368,7 @@ func (a *LDAPAuthenticator) getUser(userAsJSON []byte, attributes []*ldap.EntryA
}
if a.RequireGroups && len(groups) == 0 {
err := errors.New("users without group membership are not allowed")
logger.AppLogger.Debug("no group for found", "user", user.Username, "err", err)
logger.AppLogger.Debug("no group found", "username", user.Username, "err", err)
return nil, err
}

Expand Down Expand Up @@ -386,7 +396,14 @@ func (a *LDAPAuthenticator) hasGroups() bool {
(a.PrimaryGroupPrefix != "" || a.SecondaryGroupPrefix != "" || a.MembershipGroupPrefix != "")
}

func (a *LDAPAuthenticator) isSFTPGoUserRequired() bool {
return a.SFTPGoUserRequirements == 1
}

func (a *LDAPAuthenticator) isUserToUpdate(u *sdk.User, groups []sdk.GroupMapping) bool {
if a.isSFTPGoUserRequired() {
return false
}
if u.ID == 0 {
return true
}
Expand Down
42 changes: 29 additions & 13 deletions authenticator/ldap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ var (
func TestLDAPAuthenticator(t *testing.T) {
baseDir := filepath.Clean(os.TempDir())
auth, err := NewAuthenticator(ldapURL, baseDN, username, password, 0, false, baseDir, 2, searchQuery,
[]string{groupAttribute}, nil, primaryGroupPrefix, secondaryGroupPrefix, membershipGroupPrefix, true)
[]string{groupAttribute}, nil, primaryGroupPrefix, secondaryGroupPrefix, membershipGroupPrefix,
true, 0)
require.NoError(t, err)
require.Nil(t, auth.tlsConfig.RootCAs)
defer func() {
Expand All @@ -91,6 +92,12 @@ func TestLDAPAuthenticator(t *testing.T) {
e, ok := err.(*ldap.Error)
require.True(t, ok)
require.Equal(t, uint16(49), e.ResultCode)
auth.SFTPGoUserRequirements = 1
_, err = auth.CheckUserAndPass(user1, password, "", "", []byte(`{"username":"user1"}`))
require.Error(t, err)
assert.Contains(t, err.Error(), "LDAP users not defined in SFTPGo are not allowed")

auth.SFTPGoUserRequirements = 0
userJSON, err := auth.CheckUserAndPass(user1, password, "", "", []byte(`{"username":"user1"}`))
require.NoError(t, err)
var user sdk.User
Expand Down Expand Up @@ -158,7 +165,8 @@ func TestLDAPAuthenticator(t *testing.T) {

func TestPreserveUserChanges(t *testing.T) {
auth, err := NewAuthenticator(ldapURL, baseDN, username, password, 0, false, "", 0, searchQuery,
[]string{groupAttribute}, nil, primaryGroupPrefix, secondaryGroupPrefix, membershipGroupPrefix, false)
[]string{groupAttribute}, nil, primaryGroupPrefix, secondaryGroupPrefix, membershipGroupPrefix,
false, 0)
require.NoError(t, err)
userJSON, err := auth.CheckUserAndPass(user1, password, "", "", []byte(`{"username":"user1"}`))
require.NoError(t, err)
Expand All @@ -184,7 +192,8 @@ func TestPreserveUserChanges(t *testing.T) {

func TestLDAPS(t *testing.T) {
auth, err := NewAuthenticator(ldapsURL, baseDN, username, password, 0, true, "", 0, searchQuery,
[]string{groupAttribute}, nil, primaryGroupPrefix, secondaryGroupPrefix, membershipGroupPrefix, false)
[]string{groupAttribute}, nil, primaryGroupPrefix, secondaryGroupPrefix, membershipGroupPrefix,
false, 0)
require.NoError(t, err)
l, err := auth.connect()
require.NoError(t, err)
Expand All @@ -194,7 +203,8 @@ func TestLDAPS(t *testing.T) {

func TestLDAPConnectionErrors(t *testing.T) {
auth, err := NewAuthenticator([]string{"ldap://localhost:3892"}, baseDN, username, password, 0, true, "", 0, searchQuery,
[]string{groupAttribute}, nil, primaryGroupPrefix, secondaryGroupPrefix, membershipGroupPrefix, false)
[]string{groupAttribute}, nil, primaryGroupPrefix, secondaryGroupPrefix, membershipGroupPrefix,
false, 0)
require.NoError(t, err)
_, err = auth.CheckUserAndPass(user1, password, "", "", nil)
require.Error(t, err)
Expand All @@ -207,7 +217,8 @@ func TestLDAPConnectionErrors(t *testing.T) {
func TestStartTLS(t *testing.T) {
// glauth does not support STARTTLS
auth, err := NewAuthenticator(ldapURL, baseDN, username, password, 1, true, "", 0, searchQuery,
[]string{groupAttribute}, nil, primaryGroupPrefix, secondaryGroupPrefix, membershipGroupPrefix, false)
[]string{groupAttribute}, nil, primaryGroupPrefix, secondaryGroupPrefix, membershipGroupPrefix,
false, 0)
require.NoError(t, err)
_, err = auth.connect()
require.Error(t, err)
Expand All @@ -217,10 +228,10 @@ func TestStartTLS(t *testing.T) {
}

func TestValidation(t *testing.T) {
_, err := NewAuthenticator(nil, "", "", "", 0, false, "", 0, "", nil, nil, "", "", "", false)
_, err := NewAuthenticator(nil, "", "", "", 0, false, "", 0, "", nil, nil, "", "", "", false, 0)
require.Error(t, err)
assert.Contains(t, err.Error(), "dial URL is required")
_, err = NewAuthenticator([]string{"", ""}, "", "", "", 0, false, "", 0, "", nil, nil, "", "", "", false)
_, err = NewAuthenticator([]string{"", ""}, "", "", "", 0, false, "", 0, "", nil, nil, "", "", "", false, 0)
require.Error(t, err)
assert.Contains(t, err.Error(), "dial URL is required")
a := LDAPAuthenticator{
Expand Down Expand Up @@ -291,10 +302,14 @@ func TestUserToUpdate(t *testing.T) {
},
}
a := LDAPAuthenticator{
GroupAttributes: []string{"memberOf"},
PrimaryGroupPrefix: "sftpgo_primary",
GroupAttributes: []string{"memberOf"},
PrimaryGroupPrefix: "sftpgo_primary",
SFTPGoUserRequirements: 1,
}
res := a.isUserToUpdate(u, nil)
require.False(t, res)
a.SFTPGoUserRequirements = 0
res = a.isUserToUpdate(u, nil)
require.True(t, res)
u.Password = password
res = a.isUserToUpdate(u, nil)
Expand Down Expand Up @@ -347,17 +362,17 @@ func TestGetCNFromDN(t *testing.T) {
func TestLoadCACerts(t *testing.T) {
caCrtPath := "testcacrt"
_, err := NewAuthenticator(ldapURL, baseDN, username, password, 0, true, "", 0,
searchQuery, nil, []string{caCrtPath}, "", "", "", false)
searchQuery, nil, []string{caCrtPath}, "", "", "", false, 0)
require.Error(t, err)
assert.Contains(t, err.Error(), "is not an absolute path")
caCrtPath = filepath.Join(os.TempDir(), caCrtPath)
_, err = NewAuthenticator(ldapURL, baseDN, username, password, 0, true, "", 0,
searchQuery, nil, []string{caCrtPath}, "", "", "", false)
searchQuery, nil, []string{caCrtPath}, "", "", "", false, 0)
require.ErrorIs(t, err, fs.ErrNotExist)
err = os.WriteFile(caCrtPath, []byte(caCRT), 0600)
require.NoError(t, err)
auth, err := NewAuthenticator(ldapURL, baseDN, username, password, 0, true, "", 0,
searchQuery, nil, []string{caCrtPath}, "", "", "", false)
searchQuery, nil, []string{caCrtPath}, "", "", "", false, 0)
require.NoError(t, err)
require.NotNil(t, auth.tlsConfig.RootCAs)
err = os.Remove(caCrtPath)
Expand All @@ -366,7 +381,8 @@ func TestLoadCACerts(t *testing.T) {

func TestLDAPMonitor(t *testing.T) {
auth, err := NewAuthenticator(multipleLDAPURLs, baseDN, username, password, 0, false, "", 2, searchQuery,
[]string{groupAttribute}, nil, primaryGroupPrefix, secondaryGroupPrefix, membershipGroupPrefix, true)
[]string{groupAttribute}, nil, primaryGroupPrefix, secondaryGroupPrefix, membershipGroupPrefix,
true, 0)
require.NoError(t, err)
defer auth.Cleanup()

Expand Down
9 changes: 8 additions & 1 deletion cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ var (
secondaryGroupPrefix string
membershipGroupPrefix string
requireGroupMembership bool
sftpgoUserRequirements int

rootCmd = &cli.App{
Name: "sftpgo-plugin-auth",
Expand Down Expand Up @@ -139,6 +140,12 @@ var (
Destination: &requireGroupMembership,
EnvVars: []string{envPrefix + "REQUIRE_GROUPS"},
},
&cli.IntFlag{
Name: "user-requirements",
Usage: "Requirements for SFTPGo users. 1 means users must be already defined in SFTPGo",
Destination: &sftpgoUserRequirements,
EnvVars: []string{envPrefix + "USER_REQUIREMENTS"},
},
&cli.IntFlag{
Name: "starttls",
Usage: "STARTTLS is the preferred method of encrypting an LDAP connection. Use it instead of using the ldaps:// URL schema",
Expand Down Expand Up @@ -176,7 +183,7 @@ var (
a, err := authenticator.NewAuthenticator(ldapURL.Value(), ldapBaseDN, ldapUsername, ldapPassword, startTLS,
skipTLSVerify == 1, usersBaseDir, cacheTime, ldapSearchQuery, ldapGroupAttributes.Value(),
caCertificates.Value(), primaryGroupPrefix, secondaryGroupPrefix, membershipGroupPrefix,
requireGroupMembership)
requireGroupMembership, sftpgoUserRequirements)
if err != nil {
logger.AppLogger.Error("unable to create the authenticator", "err", err)
return err
Expand Down
10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/go-ldap/ldap/v3 v3.4.8
github.com/hashicorp/go-hclog v1.6.3
github.com/hashicorp/go-plugin v1.6.1
github.com/sftpgo/sdk v0.1.7
github.com/sftpgo/sdk v0.1.8
github.com/stretchr/testify v1.9.0
github.com/urfave/cli/v2 v2.27.2
)
Expand All @@ -26,12 +26,12 @@ require (
github.com/oklog/run v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
google.golang.org/grpc v1.64.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
20 changes: 10 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sftpgo/sdk v0.1.7 h1:lzOKBDnKb1PpSMlskqCPxBYKxVWz34uMBhT78r/13iA=
github.com/sftpgo/sdk v0.1.7/go.mod h1:ler/KG6kMLlsOs/8s6dVN3oom+z+NkbXBVWO//Cv/WA=
github.com/sftpgo/sdk v0.1.8 h1:HAywJl9jZnigFGztA/CWLieOW+R+HH6js6o6/qYvuSY=
github.com/sftpgo/sdk v0.1.8/go.mod h1:Isl0IEzS/Muvh8Fr4X+NWFsOS/fZQHRD4oPQpoY7C4g=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
Expand All @@ -79,8 +79,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
Expand Down Expand Up @@ -121,8 +121,8 @@ 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.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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=
Expand All @@ -135,15 +135,15 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
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.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
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=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 h1:mxSlqyb8ZAHsYDCfiXN1EDdNTdvjUJSLY+OnAUtYNYA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
Expand Down

0 comments on commit e80408d

Please sign in to comment.