diff --git a/go.mod b/go.mod index a0f0a6f670..118eeeed9a 100644 --- a/go.mod +++ b/go.mod @@ -97,7 +97,7 @@ require ( github.com/hashicorp/go-kms-wrapping/extras/kms/v2 v2.0.0-20221122211539-47c893099f13 github.com/hashicorp/go-version v1.3.0 github.com/hashicorp/nodeenrollment v0.2.0 - github.com/jimlambrt/gldap v0.1.2 + github.com/jimlambrt/gldap v0.1.5 github.com/kelseyhightower/envconfig v1.4.0 github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 @@ -108,7 +108,7 @@ require ( github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect github.com/AlecAivazis/survey/v2 v2.2.9 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e // indirect + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect @@ -119,6 +119,7 @@ require ( github.com/armon/go-radix v1.0.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect + github.com/cenkalti/backoff v2.2.1+incompatible // indirect github.com/cenkalti/backoff/v3 v3.0.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/containerd/continuity v0.3.0 // indirect @@ -132,7 +133,7 @@ require ( github.com/docker/go-units v0.4.0 // indirect github.com/dvsekhvalnov/jose2go v1.5.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect - github.com/go-ldap/ldap/v3 v3.4.3 // indirect + github.com/go-ldap/ldap/v3 v3.4.4 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/glog v1.0.0 // indirect github.com/golang/snappy v0.0.4 // indirect diff --git a/go.sum b/go.sum index cdb9b9835b..3852720300 100644 --- a/go.sum +++ b/go.sum @@ -68,6 +68,9 @@ github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e h1:ZU22z/2YRFLyf/P4ZwUYSdNCWsMEI0VeyrFoI2rAhJQ= github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/ClickHouse/clickhouse-go v1.4.3/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= @@ -192,6 +195,8 @@ github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7 github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= @@ -454,6 +459,8 @@ github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp github.com/go-ldap/ldap/v3 v3.1.10/go.mod h1:5Zun81jBTabRaI8lzN7E1JjyEl1g6zI6u9pd8luAK4Q= github.com/go-ldap/ldap/v3 v3.4.3 h1:JCKUtJPIcyOuG7ctGabLKMgIlKnGumD/iGjuWeEruDI= github.com/go-ldap/ldap/v3 v3.4.3/go.mod h1:7LdHfVt6iIOESVEe3Bs4Jp2sHEKgDeduAhgM1/f9qmo= +github.com/go-ldap/ldap/v3 v3.4.4 h1:qPjipEpt+qDa6SI/h1fzuGWoRUY+qqQ9sOZq67/PYUs= +github.com/go-ldap/ldap/v3 v3.4.4/go.mod h1:fe1MsuN5eJJ1FeLT/LEBVdWfNWKh459R7aXgXtJC+aI= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= @@ -870,6 +877,8 @@ github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyX github.com/jhump/protoreflect v1.9.1-0.20210817181203-db1a327a393e h1:Yb4fEGk+GtBSNuvy5rs0ZJt/jtopc/z9azQaj3xbies= github.com/jimlambrt/gldap v0.1.2 h1:Xprug+i9WdvdQd8u2bi05JVbllZ+SHhNu4alDY38+Kw= github.com/jimlambrt/gldap v0.1.2/go.mod h1:sKo9VprcJwZRj7OoE7p8YLaPEeNxw3WIEY42NS/iV7E= +github.com/jimlambrt/gldap v0.1.5 h1:m7473IVYxbNvcOWpGQ4uq0fLoiSEdC8Zbbv/mujVO8U= +github.com/jimlambrt/gldap v0.1.5/go.mod h1:ia/l4Jhm+tdupLvZe7tRCbpv+HyXr1B5QFirsewfWEA= github.com/jinzhu/gorm v1.9.12 h1:Drgk1clyWT9t9ERbzHza6Mj/8FY/CqMyVzOiHviMo6Q= github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -1409,6 +1418,7 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= diff --git a/internal/auth/additional_verification_test.go b/internal/auth/additional_verification_test.go index 7bc7e40e0f..2f24998d4c 100644 --- a/internal/auth/additional_verification_test.go +++ b/internal/auth/additional_verification_test.go @@ -123,6 +123,7 @@ func TestRecursiveListingDifferentOutputFields(t *testing.T) { tc := controller.NewTestController(t, &controller.TestControllerOpts{ // Disable this to avoid having to deal with sorting them in the test DisableOidcAuthMethodCreation: true, + DisableLdapAuthMethodCreation: true, }) defer tc.Shutdown() diff --git a/internal/auth/ldap/options.go b/internal/auth/ldap/options.go index f4bc7b3f9e..78e8ad4de5 100644 --- a/internal/auth/ldap/options.go +++ b/internal/auth/ldap/options.go @@ -45,6 +45,7 @@ type options struct { withAccountAttributeMap map[string]AccountToAttribute withMemberOfGroups string withUrls []string + withPublicId string } // Option - how options are passed as args @@ -363,3 +364,12 @@ func WithMemberOfGroups(ctx context.Context, groupName ...string) Option { return nil } } + +// WithPublicId provides an option for passing a public id to the operation +func WithPublicId(ctx context.Context, publicId string) Option { + const op = "ldap.WithPublicId" + return func(o *options) error { + o.withPublicId = publicId + return nil + } +} diff --git a/internal/auth/ldap/options_test.go b/internal/auth/ldap/options_test.go index 21483ef795..acf132b06e 100644 --- a/internal/auth/ldap/options_test.go +++ b/internal/auth/ldap/options_test.go @@ -315,4 +315,13 @@ func Test_getOpts(t *testing.T) { testOpts.withMemberOfGroups = "[\"test\"]" assert.Equal(opts, testOpts) }) + t.Run("WithPublicId", func(t *testing.T) { + assert := assert.New(t) + opts, err := getOpts(WithPublicId(testCtx, "test")) + require.NoError(t, err) + testOpts := getDefaultOptions() + assert.NotEqual(opts, testOpts) + testOpts.withPublicId = "test" + assert.Equal(opts, testOpts) + }) } diff --git a/internal/auth/ldap/repository_auth_method_create.go b/internal/auth/ldap/repository_auth_method_create.go index 0b6aed9b4c..5863a0137d 100644 --- a/internal/auth/ldap/repository_auth_method_create.go +++ b/internal/auth/ldap/repository_auth_method_create.go @@ -6,7 +6,9 @@ package ldap import ( "context" "fmt" + "strings" + "github.com/hashicorp/boundary/globals" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/kms" @@ -21,7 +23,7 @@ import ( // The AuthMethod's public id and version must be empty (zero values). // // All options are ignored. -func (r *Repository) CreateAuthMethod(ctx context.Context, am *AuthMethod, _ ...Option) (*AuthMethod, error) { +func (r *Repository) CreateAuthMethod(ctx context.Context, am *AuthMethod, opt ...Option) (*AuthMethod, error) { const op = "ldap.(Repository).CreateAuthMethod" switch { case am == nil: @@ -38,11 +40,22 @@ func (r *Repository) CreateAuthMethod(ctx context.Context, am *AuthMethod, _ ... return nil, errors.New(ctx, errors.InvalidParameter, op, "missing urls (there must be at least one)") } - var err error - am.PublicId, err = newAuthMethodId(ctx) + opts, err := getOpts(opt...) if err != nil { return nil, errors.Wrap(ctx, err, op) } + am.PublicId = opts.withPublicId + if am.PublicId == "" { + id, err := newAuthMethodId(ctx) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + am.PublicId = id + } else { + if !strings.HasPrefix(am.PublicId, globals.LdapAuthMethodPrefix+"_") { + return nil, errors.New(ctx, errors.InvalidParameter, op, "wrong auth method id prefix") + } + } cv, err := am.convertValueObjects(ctx) if err != nil { diff --git a/internal/auth/ldap/repository_authenticate_test.go b/internal/auth/ldap/repository_authenticate_test.go index 7b76929c18..665634fb69 100644 --- a/internal/auth/ldap/repository_authenticate_test.go +++ b/internal/auth/ldap/repository_authenticate_test.go @@ -54,7 +54,7 @@ func TestRepository_authenticate(t *testing.T) { require.NoError(t, err) testAm := TestAuthMethod(t, testConn, orgDbWrapper, org.PublicId, - []string{fmt.Sprintf("ldaps://127.0.0.1:%d", td.Port())}, + []string{fmt.Sprintf("ldaps://%s:%d", td.Host(), td.Port())}, WithCertificates(testCtx, tdCerts...), WithDiscoverDn(testCtx), WithEnableGroups(testCtx), @@ -238,7 +238,7 @@ func TestRepository_authenticate(t *testing.T) { t.Run("use-token-groups", func(t *testing.T) { assert, require := assert.New(t), require.New(t) amWithTokenGroups := TestAuthMethod(t, testConn, orgDbWrapper, org.PublicId, - []string{fmt.Sprintf("ldaps://127.0.0.1:%d", td.Port())}, + []string{fmt.Sprintf("ldaps://%s:%d", td.Host(), td.Port())}, WithCertificates(testCtx, tdCerts...), WithDiscoverDn(testCtx), WithEnableGroups(testCtx), @@ -270,7 +270,7 @@ func TestRepository_authenticate(t *testing.T) { assert, require := assert.New(t), require.New(t) amWithNoCerts := TestAuthMethod(t, testConn, orgDbWrapper, org.PublicId, - []string{fmt.Sprintf("ldaps://127.0.0.1:%d", td.Port())}, + []string{fmt.Sprintf("ldaps://%s:%d", td.Host(), td.Port())}, WithDiscoverDn(testCtx), WithEnableGroups(testCtx), WithUserDn(testCtx, testdirectory.DefaultUserDN), diff --git a/internal/auth/ldap/service_authenticate_test.go b/internal/auth/ldap/service_authenticate_test.go index 5719a72619..b8d67c50c7 100644 --- a/internal/auth/ldap/service_authenticate_test.go +++ b/internal/auth/ldap/service_authenticate_test.go @@ -62,7 +62,7 @@ func TestAuthenticate(t *testing.T) { td.SetGroups(groups...) testPrimaryAuthMethod := TestAuthMethod(t, testConn, orgDbWrapper, org.PublicId, - []string{fmt.Sprintf("ldaps://127.0.0.1:%d", td.Port())}, + []string{fmt.Sprintf("ldaps://%s:%d", td.Host(), td.Port())}, WithCertificates(testCtx, tdCerts...), WithDiscoverDn(testCtx), WithEnableGroups(testCtx), @@ -72,7 +72,7 @@ func TestAuthenticate(t *testing.T) { iam.TestSetPrimaryAuthMethod(t, iamRepo, org, testPrimaryAuthMethod.PublicId) testNotPrimaryAuthMethod := TestAuthMethod(t, testConn, orgDbWrapper, org.PublicId, - []string{fmt.Sprintf("ldaps://127.0.0.1:%d", td.Port())}, + []string{fmt.Sprintf("ldaps://%s:%d", td.Host(), td.Port())}, WithCertificates(testCtx, tdCerts...), WithDiscoverDn(testCtx), WithEnableGroups(testCtx), diff --git a/internal/cmd/base/dev.go b/internal/cmd/base/dev.go index f7440f7f33..d14da6eedb 100644 --- a/internal/cmd/base/dev.go +++ b/internal/cmd/base/dev.go @@ -13,6 +13,7 @@ import ( "strings" "github.com/hashicorp/boundary/globals" + "github.com/hashicorp/boundary/internal/auth/ldap" "github.com/hashicorp/boundary/internal/auth/oidc" "github.com/hashicorp/boundary/internal/cmd/base/internal/docker" "github.com/hashicorp/boundary/internal/db" @@ -24,6 +25,8 @@ import ( "github.com/hashicorp/boundary/testing/dbtest" capoidc "github.com/hashicorp/cap/oidc" "github.com/hashicorp/go-multierror" + "github.com/jimlambrt/gldap" + "github.com/jimlambrt/gldap/testdirectory" ) func (b *Server) CreateDevDatabase(ctx context.Context, opt ...Option) error { @@ -135,6 +138,12 @@ func (b *Server) CreateDevDatabase(ctx context.Context, opt ...Option) error { } } + if !opts.withSkipLdapAuthMethodCreation { + if err := b.CreateDevLdapAuthMethod(ctx); err != nil { + return err + } + } + if opts.withSkipScopesCreation { // now that we have passed all the error cases, reset c to be a noop so the // defer doesn't do anything. @@ -177,6 +186,208 @@ func (b *Server) CreateDevDatabase(ctx context.Context, opt ...Option) error { return nil } +type ldapSetup struct { + testDirectory *testdirectory.Directory + authMethod *ldap.AuthMethod +} + +func (b *Server) CreateDevLdapAuthMethod(ctx context.Context) error { + var ( + err error + port int + host string + createUnpriv bool + ) + + if b.DevLdapAuthMethodId == "" { + b.DevLdapAuthMethodId, err = db.NewPublicId(globals.LdapAuthMethodPrefix) + if err != nil { + return fmt.Errorf("error generating initial ldap auth method id: %w", err) + } + } + b.InfoKeys = append(b.InfoKeys, "generated ldap auth method id") + b.Info["generated ldap auth method id"] = b.DevLdapAuthMethodId + + switch { + case b.DevUnprivilegedLoginName == "", + b.DevUnprivilegedPassword == "", + b.DevUnprivilegedUserId == "", + b.DevUnprivilegedOidcAccountId == "": + + default: + createUnpriv = true + } + + // Trawl through the listeners and find the api listener so we can use the + // same host name/IP + { + for _, ln := range b.Listeners { + purpose := strings.ToLower(ln.Config.Purpose[0]) + if purpose != "api" { + continue + } + host, _, err = net.SplitHostPort(ln.Config.Address) + if err != nil { + if strings.Contains(err.Error(), "missing port") { + host = ln.Config.Address + } else { + return fmt.Errorf("error splitting host/port: %w", err) + } + } + } + if host == "" { + return fmt.Errorf("could not determine address to use for built-in oidc dev listener") + } + } + + tb := &oidcLogger{} + + port = testdirectory.FreePort(tb) + b.DevLdapSetup.testDirectory = testdirectory.Start(tb, + testdirectory.WithNoTLS(tb), + testdirectory.WithHost(tb, host), + testdirectory.WithPort(tb, port), + testdirectory.WithDefaults(tb, &testdirectory.Defaults{AllowAnonymousBind: true}), + ) + b.ShutdownFuncs = append(b.ShutdownFuncs, func() error { + b.DevLdapSetup.testDirectory.Stop() + return nil + }) + + groups := []*gldap.Entry{ + testdirectory.NewGroup(tb, "admin", []string{"admin"}), + } + + createUserFn := func(userName, passwd string, withMembersOf []string) *gldap.Entry { + entryAttrs := map[string][]string{ + "name": {userName}, + "email": {fmt.Sprintf("%s@localhost", userName)}, + "password": {passwd}, + } + if len(withMembersOf) > 0 { + entryAttrs["memberOf"] = withMembersOf + } + DN := fmt.Sprintf("%s=%s,%s", testdirectory.DefaultUserAttr, userName, testdirectory.DefaultUserDN) + return gldap.NewEntry( + DN, + entryAttrs, + ) + } + users := []*gldap.Entry{ + createUserFn(b.DevLoginName, b.DevPassword, []string{"admin"}), + } + + if createUnpriv { + users = append(users, createUserFn(b.DevUnprivilegedLoginName, b.DevUnprivilegedPassword, nil)) + } + b.DevLdapSetup.testDirectory.SetUsers(users...) + b.DevLdapSetup.testDirectory.SetGroups(groups...) + + // Create auth method and link accounts + { + b.DevLdapSetup.authMethod, err = b.createInitialLdapAuthMethod(ctx, host, port, createUnpriv) + if err != nil { + return fmt.Errorf("error creating initial ldap auth method: %w", err) + } + } + + return nil +} + +func (b *Server) createInitialLdapAuthMethod(ctx context.Context, host string, port int, createUnprivAccount bool) (*ldap.AuthMethod, error) { + rw := db.New(b.Database) + kmsCache, err := kms.New(ctx, rw, rw) + if err != nil { + return nil, fmt.Errorf("error creating kms cache: %w", err) + } + if err := kmsCache.AddExternalWrappers( + b.Context, + kms.WithRootWrapper(b.RootKms), + ); err != nil { + return nil, fmt.Errorf("error adding config keys to kms: %w", err) + } + ldapRepo, err := ldap.NewRepository(ctx, rw, rw, kmsCache) + if err != nil { + return nil, fmt.Errorf("error creating ldap repo: %w", err) + } + + u, err := url.Parse(fmt.Sprintf("ldap://%s:%d", host, port)) + if err != nil { + return nil, fmt.Errorf("error creating ldap url: %w", err) + } + authMethod, err := ldap.NewAuthMethod( + ctx, + scope.Global.String(), + ldap.WithUrls(ctx, u), + ldap.WithName(ctx, "Generated global scope initial ldap auth method"), + ldap.WithDescription(ctx, "Provides initial administrative and unprivileged authentication into Boundary"), + ldap.WithDiscoverDn(ctx), + ldap.WithUserDn(ctx, testdirectory.DefaultUserDN), + ldap.WithGroupDn(ctx, testdirectory.DefaultGroupDN), + ) + if err != nil { + return nil, fmt.Errorf("error creating new in memory ldap auth method: %w", err) + } + if b.DevLdapAuthMethodId == "" { + b.DevLdapAuthMethodId, err = db.NewPublicId(globals.LdapAuthMethodPrefix) + if err != nil { + return nil, fmt.Errorf("error generating initial ldap auth method id: %w", err) + } + } + + createdAuthMethod, err := ldapRepo.CreateAuthMethod(ctx, authMethod, ldap.WithPublicId(ctx, b.DevLdapAuthMethodId)) + if err != nil { + return nil, fmt.Errorf("error saving ldap auth method: %w", err) + } + + // create dev ldap accounts + { + createAndLinkAccount := func(loginName, userId, typ string) error { + acct, err := ldap.NewAccount( + ctx, + createdAuthMethod.GetScopeId(), + createdAuthMethod.GetPublicId(), + loginName, + ldap.WithDescription(ctx, fmt.Sprintf("Initial %s ldap account", typ)), + ) + if err != nil { + return fmt.Errorf("error generating %s ldap account: %w", typ, err) + } + acct, err = ldapRepo.CreateAccount(ctx, acct) + if err != nil { + return fmt.Errorf("error creating %s ldap account: %w", typ, err) + } + + // Link accounts to existing user + iamRepo, err := iam.NewRepository(rw, rw, kmsCache) + if err != nil { + return fmt.Errorf("unable to create iam repo: %w", err) + } + + u, _, err := iamRepo.LookupUser(ctx, userId) + if err != nil { + return fmt.Errorf("error looking up %s user: %w", typ, err) + } + if _, err = iamRepo.AddUserAccounts(ctx, u.GetPublicId(), u.GetVersion(), []string{acct.GetPublicId()}); err != nil { + return fmt.Errorf("error associating initial %s user with account: %w", typ, err) + } + + return nil + } + + if err := createAndLinkAccount(b.DevLoginName, b.DevUserId, "admin"); err != nil { + return nil, err + } + if createUnprivAccount { + if err := createAndLinkAccount(b.DevUnprivilegedLoginName, b.DevUnprivilegedUserId, "unprivileged"); err != nil { + return nil, err + } + } + + } + return createdAuthMethod, nil +} + type oidcSetup struct { clientId string clientSecret oidc.ClientSecret @@ -469,3 +680,7 @@ func (_ *oidcLogger) caller() event.Op { } return caller } + +func (l *oidcLogger) Log(args ...interface{}) { + event.WriteSysEvent(l.Ctx, l.caller(), fmt.Sprintf("%v", args...)) +} diff --git a/internal/cmd/base/option.go b/internal/cmd/base/option.go index 553814fd84..97ccc3208a 100644 --- a/internal/cmd/base/option.go +++ b/internal/cmd/base/option.go @@ -30,6 +30,7 @@ type Options struct { withSkipDatabaseDestruction bool withSkipAuthMethodCreation bool withSkipOidcAuthMethodCreation bool + withSkipLdapAuthMethodCreation bool withSkipScopesCreation bool withSkipHostResourcesCreation bool withSkipTargetCreation bool @@ -93,6 +94,14 @@ func WithSkipOidcAuthMethodCreation() Option { } } +// WithSkipLdapAuthMethodCreation tells the command not to instantiate an LDAP auth +// method on first run, useful in some tests. +func WithSkipLdapAuthMethodCreation() Option { + return func(o *Options) { + o.withSkipLdapAuthMethodCreation = true + } +} + // WithSkipScopesCreation tells the command not to instantiate scopes on first // run. func WithSkipScopesCreation() Option { diff --git a/internal/cmd/base/option_test.go b/internal/cmd/base/option_test.go index 0208f73ca1..a0a9bc9216 100644 --- a/internal/cmd/base/option_test.go +++ b/internal/cmd/base/option_test.go @@ -85,6 +85,13 @@ func Test_GetOpts(t *testing.T) { testOpts.withSkipOidcAuthMethodCreation = true assert.Equal(opts, testOpts) }) + t.Run("WithSkipLdapAuthMethodCreation", func(t *testing.T) { + assert := assert.New(t) + opts := getOpts(WithSkipLdapAuthMethodCreation()) + testOpts := getDefaultOptions() + testOpts.withSkipLdapAuthMethodCreation = true + assert.Equal(opts, testOpts) + }) t.Run("WithSkipScopesCreation", func(t *testing.T) { assert := assert.New(t) opts := getOpts(WithSkipScopesCreation()) diff --git a/internal/cmd/base/servers.go b/internal/cmd/base/servers.go index 8b4f200d4c..d17c6d0292 100644 --- a/internal/cmd/base/servers.go +++ b/internal/cmd/base/servers.go @@ -101,6 +101,7 @@ type Server struct { DevPasswordAuthMethodId string DevOidcAuthMethodId string + DevLdapAuthMethodId string DevLoginName string DevPassword string DevUserId string @@ -129,6 +130,7 @@ type Server struct { HostPlugins map[string]plgpb.HostPluginServiceClient DevOidcSetup oidcSetup + DevLdapSetup ldapSetup DatabaseUrl string DatabaseMaxOpenConnections int diff --git a/internal/cmd/commands/dev/dev.go b/internal/cmd/commands/dev/dev.go index b47cce7bbd..202193a2e9 100644 --- a/internal/cmd/commands/dev/dev.go +++ b/internal/cmd/commands/dev/dev.go @@ -460,6 +460,7 @@ func (c *Command) Run(args []string) int { } c.DevPasswordAuthMethodId = fmt.Sprintf("%s_%s", globals.PasswordAuthMethodPrefix, c.flagIdSuffix) c.DevOidcAuthMethodId = fmt.Sprintf("%s_%s", globals.OidcAuthMethodPrefix, c.flagIdSuffix) + c.DevLdapAuthMethodId = fmt.Sprintf("%s_%s", globals.LdapAuthMethodPrefix, c.flagIdSuffix) c.DevUserId = fmt.Sprintf("%s_%s", globals.UserPrefix, c.flagIdSuffix) c.DevPasswordAccountId = fmt.Sprintf("%s_%s", globals.PasswordAccountPrefix, c.flagIdSuffix) c.DevOidcAccountId = fmt.Sprintf("%s_%s", globals.OidcAccountPrefix, c.flagIdSuffix) diff --git a/internal/cmd/commands/server/server_test.go b/internal/cmd/commands/server/server_test.go index fa6dad2dd9..466b7d8484 100644 --- a/internal/cmd/commands/server/server_test.go +++ b/internal/cmd/commands/server/server_test.go @@ -82,7 +82,7 @@ func testServerCommand(t *testing.T, opts testServerCommandOpts) *Command { cmd.Server.DevSecondaryTargetId = defaultSecondaryTestTargetId } - err = cmd.CreateDevDatabase(cmd.Context, base.WithDatabaseTemplate("boundary_template"), base.WithSkipOidcAuthMethodCreation()) + err = cmd.CreateDevDatabase(cmd.Context, base.WithDatabaseTemplate("boundary_template"), base.WithSkipOidcAuthMethodCreation(), base.WithSkipLdapAuthMethodCreation()) if err != nil { if cmd.DevDatabaseCleanupFunc != nil { require.NoError(cmd.DevDatabaseCleanupFunc()) diff --git a/internal/daemon/controller/handlers/authmethods/ldap_test.go b/internal/daemon/controller/handlers/authmethods/ldap_test.go index 77441f0a5b..a89a62bd10 100644 --- a/internal/daemon/controller/handlers/authmethods/ldap_test.go +++ b/internal/daemon/controller/handlers/authmethods/ldap_test.go @@ -790,7 +790,7 @@ func TestAuthenticate_Ldap(t *testing.T) { require.NoError(t, err) testAm := ldap.TestAuthMethod(t, testConn, orgDbWrapper, o.PublicId, - []string{fmt.Sprintf("ldaps://127.0.0.1:%d", td.Port())}, + []string{fmt.Sprintf("ldaps://%s:%d", td.Host(), td.Port())}, ldap.WithCertificates(testCtx, tdCerts...), ldap.WithDiscoverDn(testCtx), ldap.WithEnableGroups(testCtx), diff --git a/internal/daemon/controller/testing.go b/internal/daemon/controller/testing.go index dcf3a2e462..13bc92d0ed 100644 --- a/internal/daemon/controller/testing.go +++ b/internal/daemon/controller/testing.go @@ -50,6 +50,7 @@ const ( DefaultProjectId = "p_1234567890" DefaultTestPasswordAuthMethodId = "ampw_1234567890" DefaultTestOidcAuthMethodId = "amoidc_1234567890" + DefaultTestLdapAuthMethodId = globals.LdapAuthMethodPrefix + "_1234567890" DefaultTestLoginName = "admin" DefaultTestUnprivilegedLoginName = "user" DefaultTestPassword = "passpass" @@ -355,6 +356,9 @@ type TestControllerOpts struct { // DefaultOidcAuthMethodId is the default OIDC method ID to use, if set. DefaultOidcAuthMethodId string + // DefaultLdapAuthMethodId is the default LDAP method ID to use, if set. + DefaultLdapAuthMethodId string + // DefaultLoginName is the login name used when creating the default admin account. DefaultLoginName string @@ -376,6 +380,10 @@ type TestControllerOpts struct { // OIDC listener. Useful for e.g. unix listener tests. DisableOidcAuthMethodCreation bool + // DisableLdapAuthMethodCreation can be set true to disable the built-in + // ldap listener. Useful for e.g. unix listener tests. + DisableLdapAuthMethodCreation bool + // DisableScopesCreation can be set true to disable creating scopes // automatically. DisableScopesCreation bool @@ -564,6 +572,11 @@ func TestControllerConfig(t testing.TB, ctx context.Context, tc *TestController, } else { tc.b.DevOidcAuthMethodId = DefaultTestOidcAuthMethodId } + if opts.DefaultLdapAuthMethodId != "" { + tc.b.DevLdapAuthMethodId = opts.DefaultLdapAuthMethodId + } else { + tc.b.DevLdapAuthMethodId = DefaultTestLdapAuthMethodId + } if opts.DefaultLoginName != "" { tc.b.DevLoginName = opts.DefaultLoginName } else { @@ -643,6 +656,7 @@ func TestControllerConfig(t testing.TB, ctx context.Context, tc *TestController, suffix := opts.InitialResourcesSuffix tc.b.DevPasswordAuthMethodId = "ampw_" + suffix tc.b.DevOidcAuthMethodId = "amoidc_" + suffix + tc.b.DevLdapAuthMethodId = globals.LdapAuthMethodPrefix + "_" + suffix tc.b.DevHostCatalogId = "hcst_" + suffix tc.b.DevHostId = "hst_" + suffix tc.b.DevHostSetId = "hsst_" + suffix @@ -714,6 +728,11 @@ func TestControllerConfig(t testing.TB, ctx context.Context, tc *TestController, t.Fatal(err) } } + if !opts.DisableLdapAuthMethodCreation { + if err := tc.b.CreateDevLdapAuthMethod(ctx); err != nil { + t.Fatal(err) + } + } if !opts.DisableScopesCreation { if _, _, err := tc.b.CreateInitialScopes(ctx); err != nil { t.Fatal(err) @@ -740,6 +759,9 @@ func TestControllerConfig(t testing.TB, ctx context.Context, tc *TestController, if opts.DisableOidcAuthMethodCreation { createOpts = append(createOpts, base.WithSkipOidcAuthMethodCreation()) } + if opts.DisableOidcAuthMethodCreation { + createOpts = append(createOpts, base.WithSkipLdapAuthMethodCreation()) + } if !opts.DisableDatabaseTemplate { createOpts = append(createOpts, base.WithDatabaseTemplate("boundary_template")) } @@ -764,6 +786,7 @@ func (tc *TestController) AddClusterControllerMember(t testing.TB, opts *TestCon DatabaseUrl: tc.c.conf.DatabaseUrl, DefaultPasswordAuthMethodId: tc.c.conf.DevPasswordAuthMethodId, DefaultOidcAuthMethodId: tc.c.conf.DevOidcAuthMethodId, + DefaultLdapAuthMethodId: tc.c.conf.DevLdapAuthMethodId, RootKms: tc.c.conf.RootKms, WorkerAuthKms: tc.c.conf.WorkerAuthKms, DownstreamWorkerAuthKms: tc.c.conf.DownstreamWorkerAuthKms, diff --git a/internal/daemon/controller/testing_test.go b/internal/daemon/controller/testing_test.go index a39e130b37..7a31e1c468 100644 --- a/internal/daemon/controller/testing_test.go +++ b/internal/daemon/controller/testing_test.go @@ -5,11 +5,16 @@ package controller import ( "bytes" + "context" "io" "os" "testing" + "github.com/hashicorp/boundary/globals" + "github.com/hashicorp/boundary/internal/auth/ldap" + "github.com/hashicorp/boundary/internal/db" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func Test_TestController(t *testing.T) { @@ -65,4 +70,19 @@ func Test_TestController(t *testing.T) { defer tc.Shutdown() })) }) + t.Run("set-default-ldap-auth-method-id", func(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + testCtx := context.Background() + testLdapAuthMethodId := globals.LdapAuthMethodPrefix + "_0123456789" + tc := NewTestController(t, &TestControllerOpts{DefaultLdapAuthMethodId: testLdapAuthMethodId}) + defer tc.Shutdown() + + testRw := db.New(tc.DbConn()) + testLdapRepo, err := ldap.NewRepository(testCtx, testRw, testRw, tc.c.kms) + require.NoError(err) + got, err := testLdapRepo.LookupAuthMethod(testCtx, testLdapAuthMethodId) + require.NoError(err) + assert.Equal(testLdapAuthMethodId, got.GetPublicId()) + }) } diff --git a/internal/tests/cluster/recursive_anon_listing_test.go b/internal/tests/cluster/recursive_anon_listing_test.go index 5c9a56a3f3..04378f3fd7 100644 --- a/internal/tests/cluster/recursive_anon_listing_test.go +++ b/internal/tests/cluster/recursive_anon_listing_test.go @@ -36,7 +36,7 @@ func TestListAnonymousRecursing(t *testing.T) { l, err := amClient.List(tc.Context(), scope.Global.String(), amapi.WithRecursive(true)) require.NoError(err) require.NotNil(l) - require.Len(l.GetItems(), 3) + require.Len(l.GetItems(), 4) // Originally we also expect to see all three as anon user amClient.ApiClient().SetToken("") diff --git a/testing/controller/controller.go b/testing/controller/controller.go index a6f82ebeb4..9368138576 100644 --- a/testing/controller/controller.go +++ b/testing/controller/controller.go @@ -24,6 +24,7 @@ type option struct { setDisableDatabaseDestruction bool setDefaultPasswordAuthMethodId bool setDefaultOidcAuthMethodId bool + setDefaultLdapAuthMethodId bool setDefaultLoginName bool setDefaultPassword bool setRootKms bool @@ -51,6 +52,7 @@ func getOpts(opt ...Option) (*controller.TestControllerOpts, error) { var setDbParams bool if opts.setDefaultPasswordAuthMethodId || opts.setDefaultOidcAuthMethodId || + opts.setDefaultLdapAuthMethodId || opts.setDefaultLoginName || opts.setDefaultPassword { setDbParams = true @@ -147,6 +149,14 @@ func WithDefaultOidcAuthMethodId(id string) Option { } } +func WithDefaultLdapAuthMethodId(id string) Option { + return func(c *option) error { + c.setDefaultLdapAuthMethodId = true + c.tcOptions.DefaultLdapAuthMethodId = id + return nil + } +} + func WithDefaultLoginName(ln string) Option { return func(c *option) error { c.setDefaultLoginName = true