-
Notifications
You must be signed in to change notification settings - Fork 289
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature (auth/ldap): add repo and reading an auth method (#2718)
- Loading branch information
Showing
13 changed files
with
1,082 additions
and
137 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package ldap | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/hashicorp/boundary/internal/db" | ||
"github.com/hashicorp/boundary/internal/errors" | ||
"github.com/hashicorp/boundary/internal/kms" | ||
) | ||
|
||
// Repository is the ldap repository | ||
type Repository struct { | ||
reader db.Reader | ||
writer db.Writer | ||
kms *kms.Kms | ||
|
||
// defaultLimit provides a default for limiting the number of results returned from the repo | ||
defaultLimit int | ||
} | ||
|
||
// NewRepository creates a new ldap Repository. Supports the options: WithLimit | ||
// which sets a default limit on results returned by repo operations. | ||
func NewRepository(ctx context.Context, r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repository, error) { | ||
const op = "ldap.NewRepository" | ||
if r == nil { | ||
return nil, errors.New(ctx, errors.InvalidParameter, op, "reader is nil") | ||
} | ||
if w == nil { | ||
return nil, errors.New(ctx, errors.InvalidParameter, op, "writer is nil") | ||
} | ||
if kms == nil { | ||
return nil, errors.New(ctx, errors.InvalidParameter, op, "kms is nil") | ||
} | ||
opts, err := getOpts(opt...) | ||
if err != nil { | ||
return nil, errors.Wrap(ctx, err, op) | ||
} | ||
if opts.withLimit == 0 { | ||
// zero signals the boundary defaults should be used. | ||
opts.withLimit = db.DefaultLimit | ||
} | ||
return &Repository{ | ||
reader: r, | ||
writer: w, | ||
kms: kms, | ||
defaultLimit: opts.withLimit, | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
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(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.ClientCertificateCert != 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")) | ||
} | ||
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.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 | ||
|
||
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 | ||
GroupDn string | ||
GroupAttr string | ||
GroupFilter string | ||
ClientCertificateKey []byte | ||
ClientCertificateKeyHmac []byte | ||
ClientCertificateKeyId string | ||
ClientCertificateCert []byte | ||
BindDn string | ||
BindPassword []byte | ||
BindPasswordHmac []byte | ||
BindKeyId string | ||
} | ||
|
||
// TableName returns the table name for gorm | ||
func (agg *authMethodAgg) TableName() string { return "ldap_auth_method_with_value_obj" } |
Oops, something went wrong.