Files
threshold/internal/auth/ldap/repository_auth_method_read.go
T
Jim 86192f75eb feature (auth/ldap): add LDAP auth method along with associated accounts and managed groups (#2912)
* feature (auth): required schema changes for auth ldap method (#2669)

chore (auth/ldap): move schema changes to next avail migration number

* feature (auth/ldap): define AuthMethod and all its value objects (#2703)

* feature (auth/ldap): storage protos
* feature (auth/ldap): define AuthMethod and all its value objects

* feature (auth/ldap): add repo and reading an auth method (#2718)

* feature (auth/ldap): add Repository.CreateAuthMethod(...) and Repository.DeleteAuthMethod(...) (#2724)

* feature (auth/ldap): add Repository.UpdateAuthMethod(...) (#2739)

* feature (auth/ldap): add Account

* feature (auth/ldap): add AuthMethod.EnableGroups

* fix (auth/ldap): refactor AuthMethod.oplog to enforce proper constraints

* feature (auth/ldap): add Account repo functions

* refactor (auth/ldap): remove entry attributes from account

Realized the entry attributes could have absolutely anything in them
(including binary data) and since we absolutely don't have to have them
there's just no reason to take on the risk.

* feature (auth/ldap): add AuthMethod.UseTokenGroups

* feature (auth/ldap): add Authenticate(...)

* refactor (auth/ldap): ensure options take a context as the 1st parameter

* feature (auth/ldap): add Account attribute maps

* chore (auth/ldap): make fmt deltas

* feature (auth/ldap): add managed groups (#2760)

* tests (auth/ldap): add missing unit test to Repository.DeleteAccount(...)

Add bits to test the delete operation when you're not able to generate
oplog metadata

* feature (auth/ldap): add managed groups

fixup! feature (auth/ldap): add managed groups (#2760)

* feature (auth/ldap): service proto definition (#2761)

* feature (handlers/authmethods): add handlers for ldap auth method operations (#2794)

* feature (auth/ldap): add Account attribute maps

* chore (auth/ldap): update cap/ldap to latest version

* feature (auth/ldap): add ldap api generation definitions

* feature (authmethods): add ldap repo NewService(...)

* feature (authmethods/ldap): add proper mask_mapping to protobufs

* feature (authmethods): add support to get an ldap auth method

* refactor (auth/ldap): export TestGenerateCA(...)

* feature (authmethods): add support to create an ldap auth method

* feature (authmethods): add support to delete an ldap auth method

* feature (authmethods): add support to list ldap auth methods

* refactor (auth/ldap): make urls optional for NewAuthMethod(...)

* refactor (auth/ldap): export ldap.TestInvalidPem

* chore: make fmt changes

* fix (auth/ldap): properly handle group search config

Add constraints and tests to ensure when an ldap AuthMethod.EnableGroups
is true, and UseTokenGroups is false; that there's a GroupDn configured
for finding a user's associated groups

* feature (authmethods): add support to update an ldap auth methods

* chore (db/ldap): tmp mv migrations so there's no conflict with ongoing work

* feature (verifier): add ldap auth method to verifier bits

* fix (controller): prevent panic when controller stops when there's no listener

* feature (authmethods): add support to authenticate via ldap auth methods

* chore (migrations): fix whitespace in stmt

* chore: fmt fixup

* tests (auth/ldap): invalid err msg

* feature (cli/authmethods): add support for ldap auth-methods CRUD and authenticate (#2810)

* feature (authmethods): add CLI support for ldap auth methods CRUD

* tests (api/auth): ldap auth method classification tests

* feature (authmethods): add CLI support for ldap auth authenticate

* feature (auth/ldap): set request timeouts for ldap server connections

* feature (handlers/authmethods): handle u_anon listing properly.

* feature (account/handers): ldap account and managed group CRUDL APIs (#2852)

* feature (auth/ldap) add repository Listing of ManagedGroupMemberAccount

* feature (controller/handlers): add ldapRepoFn to accounts service

* feature (auth/ldap) register ldap managed group subtype

* feature (account/handlers): ldap account CRUDL APIs

* feature (controller/handlers): add ldapRepo to managed groups service

* feature (account/handlers): ldap managed group CRUDL APIs

* docs (domain): add LDAP accounts, auth-methods and managed groups (#2857)

* feature (ldap/cli) add ldap accounts and managed groups CRUDL commands (#2856)

* fix (handlers/authmethods): fix ldap authorized actions (#2892)

* feature (cli/ldap/authenticate): use primary auth method if none is provided (#2890)

* fix (auth/ldap): support setting the state attribute

* feature (cli/ldap/authenticate): use primary auth method if none is provided

* feature (wh/ldap) add tests for new ldap auth method and accounts (#2919)

* refactor (migrations/ldap): mv to correct directory

* chore: add copyright headers

* fix (api/authmethods/ldap): renumber new LdapAuthMethodAttributes field

* fix (auth/ldap): allow the auth method state to be updated (#2951)

* chore: update sdk and api versions for llb - this is tmp until merging

* tests (managed groups): add required errContains for new test
2023-03-15 11:40:40 -04:00

249 lines
8.9 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package ldap
import (
"context"
"fmt"
"strings"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping"
)
// LookupAuthMethod will lookup an auth method in the repo, along with its
// associated Value Objects of SigningAlgs, CallbackUrls, AudClaims and
// Certificates. If it's not found, it will return nil, nil. The
// WithUnauthenticatedUser options is supported and all other options are
// ignored.
func (r *Repository) LookupAuthMethod(ctx context.Context, publicId string, opt ...Option) (*AuthMethod, error) {
const op = "ldap.(Repository).LookupAuthMethod"
if publicId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing public id")
}
opts, err := getOpts(opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return r.lookupAuthMethod(ctx, publicId, WithUnauthenticatedUser(ctx, opts.withUnauthenticatedUser))
}
// ListAuthMethods returns a slice of AuthMethods for the scopeId. The
// WithUnauthenticatedUser, WithLimit and WithOrder options are supported and
// all other options are ignored.
func (r *Repository) ListAuthMethods(ctx context.Context, scopeIds []string, opt ...Option) ([]*AuthMethod, error) {
const op = "ldap.(Repository).ListAuthMethods"
if len(scopeIds) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope IDs")
}
authMethods, err := r.getAuthMethods(ctx, "", scopeIds, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return authMethods, nil
}
// lookupAuthMethod will lookup a single auth method
func (r *Repository) lookupAuthMethod(ctx context.Context, authMethodId string, opt ...Option) (*AuthMethod, error) {
const op = "ldap.(Repository).lookupAuthMethod"
var err error
ams, err := r.getAuthMethods(ctx, authMethodId, nil, opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
switch {
case len(ams) == 0:
return nil, nil // not an error to return no rows for a "lookup"
case len(ams) > 1:
return nil, errors.New(ctx, errors.NotSpecificIntegrity, op, fmt.Sprintf("%s matched more than 1 ", authMethodId))
default:
return ams[0], nil
}
}
// getAuthMethods allows the caller to either lookup a specific AuthMethod via
// its id or search for a set AuthMethods within a set of scopes. Passing both
// scopeIds and a authMethod is an error. The WithUnauthenticatedUser,
// WithLimit and WithOrder options are supported and all other options are
// ignored.
//
// The AuthMethod returned has its value objects populated (SigningAlgs,
// CallbackUrls, AudClaims and Certificates). The AuthMethod returned has its
// IsPrimaryAuthMethod bool set.
//
// When no record is found it returns nil, nil
func (r *Repository) getAuthMethods(ctx context.Context, authMethodId string, scopeIds []string, opt ...Option) ([]*AuthMethod, error) {
const op = "ldap.(Repository).getAuthMethods"
if authMethodId == "" && len(scopeIds) == 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing search criteria: both auth method id and scope ids are empty")
}
if authMethodId != "" && len(scopeIds) > 0 {
return nil, errors.New(ctx, errors.InvalidParameter, op, "searching for both an auth method id and scope ids is not supported")
}
const aggregateDelimiter = "|"
dbArgs := []db.Option{}
opts, err := getOpts(opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
limit := r.defaultLimit
if opts.withLimit != 0 {
// non-zero signals an override of the default limit for the repo.
limit = opts.withLimit
}
dbArgs = append(dbArgs, db.WithLimit(limit))
if opts.withOrderByCreateTime {
if opts.ascending {
dbArgs = append(dbArgs, db.WithOrder("create_time asc"))
} else {
dbArgs = append(dbArgs, db.WithOrder("create_time"))
}
}
var args []any
var where []string
switch {
case authMethodId != "":
where, args = append(where, "public_id = ?"), append(args, authMethodId)
default:
where, args = append(where, "scope_id in(?)"), append(args, scopeIds)
}
if opts.withUnauthenticatedUser {
// the caller is asking for a list of auth methods which can be returned
// to unauthenticated users (so they can authen).
where, args = append(where, "state = ?"), append(args, string(ActivePublicState))
}
var aggAuthMethods []*authMethodAgg
err = r.reader.SearchWhere(ctx, &aggAuthMethods, strings.Join(where, " and "), args, dbArgs...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
if len(aggAuthMethods) == 0 { // we're done if nothing is found.
return nil, nil
}
authMethods := make([]*AuthMethod, 0, len(aggAuthMethods))
for _, agg := range aggAuthMethods {
ccKey := struct {
Ct []byte `wrapping:"ct,certificate_key"`
Pt []byte `wrapping:"pt,certificate_key"`
}{Ct: agg.ClientCertificateKey}
if agg.ClientCertificateKey != nil {
ccWrapper, err := r.kms.GetWrapper(ctx, agg.ScopeId, kms.KeyPurposeDatabase, kms.WithKeyId(agg.ClientCertificateKeyId))
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("failed to get database wrapper for client certificate key"))
}
if err := structwrapping.UnwrapStruct(ctx, ccWrapper, &ccKey); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("failed to decrypt client certificate key"))
}
}
bindPassword := struct {
Ct []byte `wrapping:"ct,password"`
Pt []byte `wrapping:"pt,password"`
}{Ct: agg.BindPassword}
if agg.BindPassword != nil {
bindWrapper, err := r.kms.GetWrapper(ctx, agg.ScopeId, kms.KeyPurposeDatabase, kms.WithKeyId(agg.BindKeyId))
if err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("failed to get database wrapper for bind password"))
}
if err := structwrapping.UnwrapStruct(ctx, bindWrapper, &bindPassword); err != nil {
return nil, errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("failed to decrypt bind password"))
}
}
am := AllocAuthMethod()
am.PublicId = agg.PublicId
am.ScopeId = agg.ScopeId
am.IsPrimaryAuthMethod = agg.IsPrimaryAuthMethod
am.Name = agg.Name
am.Description = agg.Description
am.CreateTime = agg.CreateTime
am.UpdateTime = agg.UpdateTime
am.Version = agg.Version
am.OperationalState = agg.State
am.StartTls = agg.StartTLS
am.InsecureTls = agg.InsecureTLS
am.DiscoverDn = agg.DiscoverDn
am.AnonGroupSearch = agg.AnonGroupSearch
am.EnableGroups = agg.EnableGroups
am.UseTokenGroups = agg.UseTokenGroups
am.UpnDomain = agg.UpnDomain
if agg.Urls != "" {
am.Urls = strings.Split(agg.Urls, aggregateDelimiter)
}
if agg.Certs != "" {
am.Certificates = strings.Split(agg.Certs, aggregateDelimiter)
}
am.UserDn = agg.UserDn
am.UserAttr = agg.UserAttr
am.UserFilter = agg.UserFilter
am.GroupDn = agg.GroupDn
am.GroupAttr = agg.GroupAttr
am.GroupFilter = agg.GroupFilter
am.ClientCertificateKey = ccKey.Pt
am.ClientCertificateKeyHmac = agg.ClientCertificateKeyHmac
am.ClientCertificate = string(agg.ClientCertificateCert)
am.BindDn = agg.BindDn
am.BindPassword = string(bindPassword.Pt)
am.BindPasswordHmac = agg.BindPasswordHmac
if agg.AccountAttributeMap != "" {
am.AccountAttributeMaps = strings.Split(agg.AccountAttributeMap, aggregateDelimiter)
}
authMethods = append(authMethods, &am)
}
return authMethods, nil
}
// authMethodAgg is a view that aggregates the auth method's value objects. If
// the value object can have multiple values like Urls and Certs, then the
// string field is delimited with the aggregateDelimiter of "|"
type authMethodAgg struct {
PublicId string `gorm:"primary_key"`
ScopeId string
IsPrimaryAuthMethod bool
Name string
Description string
CreateTime *timestamp.Timestamp
UpdateTime *timestamp.Timestamp
Version uint32
State string
StartTLS bool
InsecureTLS bool
DiscoverDn bool
AnonGroupSearch bool
UpnDomain string
Urls string
Certs string
UserDn string
UserAttr string
UserFilter string
EnableGroups bool
UseTokenGroups bool
GroupDn string
GroupAttr string
GroupFilter string
ClientCertificateKey []byte
ClientCertificateKeyHmac []byte
ClientCertificateKeyId string
ClientCertificateCert []byte
BindDn string
BindPassword []byte
BindPasswordHmac []byte
BindKeyId string
AccountAttributeMap string
}
// TableName returns the table name for gorm
func (agg *authMethodAgg) TableName() string { return "ldap_auth_method_with_value_obj" }