You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/auth/ldap/auth_method.go

350 lines
12 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package ldap
import (
"context"
"fmt"
"net/url"
"github.com/hashicorp/boundary/internal/auth/ldap/store"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/oplog"
"github.com/hashicorp/boundary/internal/types/resource"
"google.golang.org/protobuf/proto"
)
// authMethodTableName defines an AuthMethod's table name.
const authMethodTableName = "auth_ldap_method"
// AuthMethod contains an LDAP auth method configuration. It is owned by a
// scope. AuthMethods MUST have at least one Url. AuthMethods MAY one or zero:
// UserEntrySearchConf, a GroupEntrySearchConf, BindCredential. AuthMethods
// may have zero to many: Accounts, Certificates,
type AuthMethod struct {
*store.AuthMethod
tableName string
}
// NewAuthMethod creates a new in memory AuthMethod assigned to a scopeId. The
// new auth method will have an OperationalState of Inactive.
//
// Supports the options: WithUrls, WithName, WithDescription, WithStartTLS,
// WithInsecureTLS, WithDiscoverDN, WithAnonGroupSearch, WithUpnDomain,
// WithUserSearchConf, WithGroupSearchConf, WithCertificates,
// WithBindCredential, WithDerefAliases, WithMaximumPageSize
// are the only valid options and all other options are ignored.
func NewAuthMethod(ctx context.Context, scopeId string, opt ...Option) (*AuthMethod, error) {
const op = "ldap.NewAuthMethod"
switch {
case scopeId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
}
opts, err := getOpts(opt...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
a := &AuthMethod{
AuthMethod: &store.AuthMethod{
ScopeId: scopeId,
Name: opts.withName,
Description: opts.withDescription,
OperationalState: string(opts.withOperationalState), // if no option is specified, a new auth method is initially inactive
Urls: opts.withUrls,
StartTls: opts.withStartTls,
InsecureTls: opts.withInsecureTls,
DiscoverDn: opts.withDiscoverDn,
AnonGroupSearch: opts.withAnonGroupSearch,
UpnDomain: opts.withUpnDomain,
UserDn: opts.withUserDn,
UserAttr: opts.withUserAttr,
UserFilter: opts.withUserFilter,
EnableGroups: opts.withEnableGroups,
UseTokenGroups: opts.withUseTokenGroups,
GroupDn: opts.withGroupDn,
GroupAttr: opts.withGroupAttr,
GroupFilter: opts.withGroupFilter,
BindDn: opts.withBindDn,
BindPassword: opts.withBindPassword,
Certificates: opts.withCertificates,
ClientCertificate: opts.withClientCertificate,
ClientCertificateKey: opts.withClientCertificateKey,
DereferenceAliases: string(opts.withDerefAliases),
MaximumPageSize: uint32(opts.withMaximumPageSize),
},
}
if len(opts.withAccountAttributeMap) > 0 {
a.AccountAttributeMaps = make([]string, 0, len(opts.withAccountAttributeMap))
for k, v := range opts.withAccountAttributeMap {
a.AccountAttributeMaps = append(a.AccountAttributeMaps, fmt.Sprintf("%s=%s", k, v))
}
}
return a, nil
}
// AllocAuthMethod makes an empty one in memory
func AllocAuthMethod() AuthMethod {
return AuthMethod{
AuthMethod: &store.AuthMethod{},
}
}
// clone an AuthMethod
func (am *AuthMethod) clone() *AuthMethod {
cp := proto.Clone(am.AuthMethod)
return &AuthMethod{
AuthMethod: cp.(*store.AuthMethod),
}
}
// TableName returns the table name (func is required by gorm)
func (am *AuthMethod) TableName() string {
if am.tableName != "" {
return am.tableName
}
return authMethodTableName
}
// SetTableName sets the table name (func is required by oplog)
func (am *AuthMethod) SetTableName(n string) {
am.tableName = n
}
// GetResourceType returns the resource type of the AuthMethod
func (am *AuthMethod) GetResourceType() resource.Type {
return resource.AuthMethod
}
// oplog will create oplog metadata for the AuthMethod.
func (am *AuthMethod) oplog(ctx context.Context, opType oplog.OpType) (oplog.Metadata, error) {
const op = "ldap.(AuthMethod).oplog"
switch {
case am == nil:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing auth method")
case opType == oplog.OpType_OP_TYPE_UNSPECIFIED:
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing op type")
case am.PublicId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing public id")
case am.ScopeId == "":
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope id")
}
metadata := oplog.Metadata{
"resource-public-id": []string{am.PublicId},
"resource-type": []string{"ldap auth method"},
"op-type": []string{opType.String()},
"scope-id": []string{am.ScopeId},
}
return metadata, nil
}
type convertedValues struct {
Urls []*Url
Certs []*Certificate
UserEntrySearchConf *UserEntrySearchConf
GroupEntrySearchConf *GroupEntrySearchConf
ClientCertificate *ClientCertificate
BindCredential *BindCredential
AccountAttributeMaps []*AccountAttributeMap
DerefAliases *DerefAliases
}
// convertValueObjects converts the embedded value objects. It will return an
// error if the AuthMethod's public id is not set.
func (am *AuthMethod) convertValueObjects(ctx context.Context) (*convertedValues, error) {
const op = "ldap.(AuthMethod).convertValueObjects"
if am.PublicId == "" {
return nil, errors.New(ctx, errors.InvalidPublicId, op, "missing public id")
}
var err error
converted := &convertedValues{}
if converted.Urls, err = am.convertUrls(ctx); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
if converted.Certs, err = am.convertCertificates(ctx); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
if am.UserDn != "" || am.UserAttr != "" || am.UserFilter != "" {
if converted.UserEntrySearchConf, err = am.convertUserEntrySearchConf(ctx); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
}
if am.GroupDn != "" || am.GroupAttr != "" || am.GroupFilter != "" {
if converted.GroupEntrySearchConf, err = am.convertGroupEntrySearchConf(ctx); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
}
if am.ClientCertificate != "" || am.ClientCertificateKey != nil {
if converted.ClientCertificate, err = am.convertClientCertificate(ctx); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
}
if am.BindDn != "" || am.BindPassword != "" {
if converted.BindCredential, err = am.convertBindCredential(ctx); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
}
if converted.AccountAttributeMaps, err = am.convertAccountAttributeMaps(ctx); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
if am.DereferenceAliases != "" {
if converted.DerefAliases, err = am.convertDerefAliases(ctx); err != nil {
return nil, errors.Wrap(ctx, err, op)
}
}
return converted, nil
}
// convertUrls converts any embedded URLs from []string to []*Url where each
// slice element is a *Url. It will return an error if the AuthMethod's public
// id is not set.
func (am *AuthMethod) convertUrls(ctx context.Context) ([]*Url, error) {
const op = "ldap.(AuthMethod).convertUrls"
if am.PublicId == "" {
return nil, errors.New(ctx, errors.InvalidPublicId, op, "missing public id")
}
newValObjs := make([]*Url, 0, len(am.Urls))
for priority, u := range am.Urls {
parsed, err := url.Parse(u)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
obj, err := NewUrl(ctx, am.PublicId, priority+1, parsed)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
newValObjs = append(newValObjs, obj)
}
return newValObjs, nil
}
// convertCertificates converts any embedded certificates from []string
// to []*Certificate. It will return an error if the AuthMethod's public id is
// not set.
func (am *AuthMethod) convertCertificates(ctx context.Context) ([]*Certificate, error) {
const op = "ldap.(AuthMethod).convertCertificates"
if am.PublicId == "" {
return nil, errors.New(ctx, errors.InvalidPublicId, op, "missing public id")
}
newValObjs := make([]*Certificate, 0, len(am.Certificates))
for _, cert := range am.Certificates {
obj, err := NewCertificate(ctx, am.PublicId, cert)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
newValObjs = append(newValObjs, obj)
}
return newValObjs, nil
}
// convertUserEntrySearchConf converts an embedded user entry search fields
// into an *UserEntrySearchConf type. It will return an error if the
// AuthMethod's public id is not set.
func (am *AuthMethod) convertUserEntrySearchConf(ctx context.Context) (*UserEntrySearchConf, error) {
const op = "ldap.(AuthMethod).convertUserEntrySearchConf"
if am.PublicId == "" {
return nil, errors.New(ctx, errors.InvalidPublicId, op, "missing public id")
}
c, err := NewUserEntrySearchConf(ctx, am.PublicId, WithUserDn(ctx, am.UserDn), WithUserAttr(ctx, am.UserAttr), WithUserFilter(ctx, am.UserFilter))
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return c, nil
}
// convertGroupEntrySearchConf converts an embedded group entry search fields
// into an *GroupEntrySearchConf type. It will return an error if the
// AuthMethod's public id is not set.
func (am *AuthMethod) convertGroupEntrySearchConf(ctx context.Context) (*GroupEntrySearchConf, error) {
const op = "ldap.(AuthMethod).convertGroupEntrySearchConf"
if am.PublicId == "" {
return nil, errors.New(ctx, errors.InvalidPublicId, op, "missing public id")
}
c, err := NewGroupEntrySearchConf(ctx, am.PublicId, WithGroupDn(ctx, am.GroupDn), WithGroupAttr(ctx, am.GroupAttr), WithGroupFilter(ctx, am.GroupFilter))
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return c, nil
}
// convertClientCertificate converts an embedded client certificate entry into
// an *ClientCertificate type. It will return an error if the AuthMethod's
// public id is not set.
func (am *AuthMethod) convertClientCertificate(ctx context.Context) (*ClientCertificate, error) {
const op = "ldap.(AuthMethod).convertClientCertificate"
if am.PublicId == "" {
return nil, errors.New(ctx, errors.InvalidPublicId, op, "missing auth method id")
}
cc, err := NewClientCertificate(ctx, am.PublicId, am.ClientCertificateKey, am.ClientCertificate)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return cc, nil
}
// convertBindCredential converts an embedded bind credential entry into
// an *BindCredential type. It will return an error if the AuthMethod's public
// id is not set.
func (am *AuthMethod) convertBindCredential(ctx context.Context) (*BindCredential, error) {
const op = "ldap.(AuthMethod).convertBindCredentials"
if am.PublicId == "" {
return nil, errors.New(ctx, errors.InvalidPublicId, op, "missing auth method id")
}
bc, err := NewBindCredential(ctx, am.PublicId, am.BindDn, []byte(am.BindPassword))
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return bc, nil
}
// convertDerefAliases converts an embedded deref aliases entry into
// an *DerefAliases type. It will return an error if the AuthMethod's public id
// is not set.
func (am *AuthMethod) convertDerefAliases(ctx context.Context) (*DerefAliases, error) {
const op = "ldap.(AuthMethod).convertDerefAliases"
if am.PublicId == "" {
return nil, errors.New(ctx, errors.InvalidPublicId, op, "missing auth method id")
}
da, err := NewDerefAliases(ctx, am.PublicId, DerefAliasType(am.DereferenceAliases))
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
return da, nil
}
// convertAccountAttributeMaps converts the embedded account attribute maps from
// []string to []*AccountAttributeMap. It will return an error if the
// AuthMethod's public id is not set or it can convert the account attribute
// maps.
func (am *AuthMethod) convertAccountAttributeMaps(ctx context.Context) ([]*AccountAttributeMap, error) {
const op = "ldap.(AuthMethod).convertAccountAttributeMaps"
if am.PublicId == "" {
return nil, errors.New(ctx, errors.InvalidPublicId, op, "missing public id")
}
acctAttribMaps := make([]*AccountAttributeMap, 0, len(am.AccountAttributeMaps))
const (
from = 0
to = 1
)
acms, err := ParseAccountAttributeMaps(ctx, am.AccountAttributeMaps...)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
for _, m := range acms {
toClaim, err := ConvertToAccountToAttribute(ctx, m.To)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
obj, err := NewAccountAttributeMap(ctx, am.PublicId, m.From, toClaim)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
acctAttribMaps = append(acctAttribMaps, obj)
}
return acctAttribMaps, nil
}