feat(ldap): adding root ca option to ldap config (#9292)

# Which Problems Are Solved

Adding ability to add a root CA to LDAP configs

# Additional Context

- Closes https://github.com/zitadel/zitadel/issues/7888

---------

Co-authored-by: Iraq Jaber <IraqJaber@gmail.com>
This commit is contained in:
Iraq 2025-02-18 10:06:50 +00:00 committed by GitHub
parent d7332d1ac4
commit 5bbb953ffb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 418 additions and 243 deletions

View File

@ -423,6 +423,7 @@ func addLDAPProviderToCommand(req *admin_pb.AddLDAPProviderRequest) command.LDAP
UserObjectClasses: req.UserObjectClasses, UserObjectClasses: req.UserObjectClasses,
UserFilters: req.UserFilters, UserFilters: req.UserFilters,
Timeout: req.Timeout.AsDuration(), Timeout: req.Timeout.AsDuration(),
RootCA: req.RootCa,
LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes), LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes),
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions), IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
} }
@ -442,6 +443,7 @@ func updateLDAPProviderToCommand(req *admin_pb.UpdateLDAPProviderRequest) comman
Timeout: req.Timeout.AsDuration(), Timeout: req.Timeout.AsDuration(),
LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes), LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes),
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions), IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
RootCA: req.RootCa,
} }
} }

View File

@ -620,6 +620,7 @@ func ldapConfigToPb(providerConfig *idp_pb.ProviderConfig, template *query.LDAPI
UserObjectClasses: template.UserObjectClasses, UserObjectClasses: template.UserObjectClasses,
UserFilters: template.UserFilters, UserFilters: template.UserFilters,
Timeout: timeout, Timeout: timeout,
RootCa: template.RootCA,
Attributes: ldapAttributesToPb(template.LDAPAttributes), Attributes: ldapAttributesToPb(template.LDAPAttributes),
}, },
} }

View File

@ -288,6 +288,7 @@ func ldapConfigToPb(idpConfig *idp_pb.IDPConfig, template *query.LDAPIDPTemplate
UserObjectClasses: template.UserObjectClasses, UserObjectClasses: template.UserObjectClasses,
UserFilters: template.UserFilters, UserFilters: template.UserFilters,
Timeout: timeout, Timeout: timeout,
RootCa: template.RootCA,
Attributes: ldapAttributesToPb(template.LDAPAttributes), Attributes: ldapAttributesToPb(template.LDAPAttributes),
}, },
} }

View File

@ -416,6 +416,7 @@ func addLDAPProviderToCommand(req *mgmt_pb.AddLDAPProviderRequest) command.LDAPP
UserObjectClasses: req.UserObjectClasses, UserObjectClasses: req.UserObjectClasses,
UserFilters: req.UserFilters, UserFilters: req.UserFilters,
Timeout: req.Timeout.AsDuration(), Timeout: req.Timeout.AsDuration(),
RootCA: req.RootCa,
LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes), LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes),
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions), IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
} }
@ -435,6 +436,7 @@ func updateLDAPProviderToCommand(req *mgmt_pb.UpdateLDAPProviderRequest) command
Timeout: req.Timeout.AsDuration(), Timeout: req.Timeout.AsDuration(),
LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes), LDAPAttributes: idp_grpc.LDAPAttributesToCommand(req.Attributes),
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions), IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
RootCA: req.RootCa,
} }
} }

View File

@ -976,6 +976,7 @@ func (l *Login) ldapProvider(ctx context.Context, identityProvider *query.IDPTem
identityProvider.UserObjectClasses, identityProvider.UserObjectClasses,
identityProvider.UserFilters, identityProvider.UserFilters,
identityProvider.Timeout, identityProvider.Timeout,
identityProvider.RootCA,
l.baseURL(ctx)+EndpointLDAPLogin+"?"+QueryAuthRequestID+"=", l.baseURL(ctx)+EndpointLDAPLogin+"?"+QueryAuthRequestID+"=",
opts..., opts...,
), nil ), nil

View File

@ -107,6 +107,7 @@ type LDAPProvider struct {
UserObjectClasses []string UserObjectClasses []string
UserFilters []string UserFilters []string
Timeout time.Duration Timeout time.Duration
RootCA []byte
LDAPAttributes idp.LDAPAttributes LDAPAttributes idp.LDAPAttributes
IDPOptions idp.Options IDPOptions idp.Options
} }

View File

@ -1,6 +1,7 @@
package command package command
import ( import (
"bytes"
"net/http" "net/http"
"reflect" "reflect"
"slices" "slices"
@ -1366,6 +1367,7 @@ type LDAPIDPWriteModel struct {
UserObjectClasses []string UserObjectClasses []string
UserFilters []string UserFilters []string
Timeout time.Duration Timeout time.Duration
RootCA []byte
idp.LDAPAttributes idp.LDAPAttributes
idp.Options idp.Options
@ -1406,6 +1408,7 @@ func (wm *LDAPIDPWriteModel) reduceAddedEvent(e *idp.LDAPIDPAddedEvent) {
wm.UserObjectClasses = e.UserObjectClasses wm.UserObjectClasses = e.UserObjectClasses
wm.UserFilters = e.UserFilters wm.UserFilters = e.UserFilters
wm.Timeout = e.Timeout wm.Timeout = e.Timeout
wm.RootCA = e.RootCA
wm.LDAPAttributes = e.LDAPAttributes wm.LDAPAttributes = e.LDAPAttributes
wm.Options = e.Options wm.Options = e.Options
wm.State = domain.IDPStateActive wm.State = domain.IDPStateActive
@ -1460,6 +1463,7 @@ func (wm *LDAPIDPWriteModel) NewChanges(
userObjectClasses []string, userObjectClasses []string,
userFilters []string, userFilters []string,
timeout time.Duration, timeout time.Duration,
rootCA []byte,
secretCrypto crypto.EncryptionAlgorithm, secretCrypto crypto.EncryptionAlgorithm,
attributes idp.LDAPAttributes, attributes idp.LDAPAttributes,
options idp.Options, options idp.Options,
@ -1501,6 +1505,9 @@ func (wm *LDAPIDPWriteModel) NewChanges(
if wm.Timeout != timeout { if wm.Timeout != timeout {
changes = append(changes, idp.ChangeLDAPTimeout(timeout)) changes = append(changes, idp.ChangeLDAPTimeout(timeout))
} }
if !bytes.Equal(wm.RootCA, rootCA) {
changes = append(changes, idp.ChangeLDAPRootCA(rootCA))
}
attrs := wm.LDAPAttributes.Changes(attributes) attrs := wm.LDAPAttributes.Changes(attributes)
if !attrs.IsZero() { if !attrs.IsZero() {
changes = append(changes, idp.ChangeLDAPAttributes(attrs)) changes = append(changes, idp.ChangeLDAPAttributes(attrs))
@ -1582,6 +1589,7 @@ func (wm *LDAPIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.Encryp
wm.UserObjectClasses, wm.UserObjectClasses,
wm.UserFilters, wm.UserFilters,
wm.Timeout, wm.Timeout,
wm.RootCA,
callbackURL, callbackURL,
opts..., opts...,
), nil ), nil

View File

@ -1556,6 +1556,7 @@ func (c *Commands) prepareAddInstanceLDAPProvider(a *instance.Aggregate, writeMo
provider.UserObjectClasses, provider.UserObjectClasses,
provider.UserFilters, provider.UserFilters,
provider.Timeout, provider.Timeout,
provider.RootCA,
provider.LDAPAttributes, provider.LDAPAttributes,
provider.IDPOptions, provider.IDPOptions,
), ),
@ -1616,6 +1617,7 @@ func (c *Commands) prepareUpdateInstanceLDAPProvider(a *instance.Aggregate, writ
provider.UserObjectClasses, provider.UserObjectClasses,
provider.UserFilters, provider.UserFilters,
provider.Timeout, provider.Timeout,
provider.RootCA,
c.idpConfigEncryption, c.idpConfigEncryption,
provider.LDAPAttributes, provider.LDAPAttributes,
provider.IDPOptions, provider.IDPOptions,

View File

@ -768,6 +768,7 @@ func (wm *InstanceLDAPIDPWriteModel) NewChangedEvent(
userObjectClasses []string, userObjectClasses []string,
userFilters []string, userFilters []string,
timeout time.Duration, timeout time.Duration,
rootCA []byte,
secretCrypto crypto.EncryptionAlgorithm, secretCrypto crypto.EncryptionAlgorithm,
attributes idp.LDAPAttributes, attributes idp.LDAPAttributes,
options idp.Options, options idp.Options,
@ -784,6 +785,7 @@ func (wm *InstanceLDAPIDPWriteModel) NewChangedEvent(
userObjectClasses, userObjectClasses,
userFilters, userFilters,
timeout, timeout,
rootCA,
secretCrypto, secretCrypto,
attributes, attributes,
options, options,

View File

@ -4260,6 +4260,7 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
[]string{"object"}, []string{"object"},
[]string{"filter"}, []string{"filter"},
time.Second*30, time.Second*30,
[]byte("certificate"),
idp.LDAPAttributes{}, idp.LDAPAttributes{},
idp.Options{}, idp.Options{},
), ),
@ -4281,6 +4282,7 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
UserObjectClasses: []string{"object"}, UserObjectClasses: []string{"object"},
UserFilters: []string{"filter"}, UserFilters: []string{"filter"},
Timeout: time.Second * 30, Timeout: time.Second * 30,
RootCA: []byte("certificate"),
}, },
}, },
res: res{ res: res{
@ -4311,6 +4313,7 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
[]string{"object"}, []string{"object"},
[]string{"filter"}, []string{"filter"},
time.Second*30, time.Second*30,
[]byte("certificate"),
idp.LDAPAttributes{ idp.LDAPAttributes{
IDAttribute: "id", IDAttribute: "id",
FirstNameAttribute: "firstName", FirstNameAttribute: "firstName",
@ -4351,6 +4354,7 @@ func TestCommandSide_AddInstanceLDAPIDP(t *testing.T) {
UserObjectClasses: []string{"object"}, UserObjectClasses: []string{"object"},
UserFilters: []string{"filter"}, UserFilters: []string{"filter"},
Timeout: time.Second * 30, Timeout: time.Second * 30,
RootCA: []byte("certificate"),
LDAPAttributes: idp.LDAPAttributes{ LDAPAttributes: idp.LDAPAttributes{
IDAttribute: "id", IDAttribute: "id",
FirstNameAttribute: "firstName", FirstNameAttribute: "firstName",
@ -4626,6 +4630,7 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
[]string{"object"}, []string{"object"},
[]string{"filter"}, []string{"filter"},
time.Second*30, time.Second*30,
[]byte("certificate"),
idp.LDAPAttributes{}, idp.LDAPAttributes{},
idp.Options{}, idp.Options{},
)), )),
@ -4645,6 +4650,7 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
UserObjectClasses: []string{"object"}, UserObjectClasses: []string{"object"},
UserFilters: []string{"filter"}, UserFilters: []string{"filter"},
Timeout: time.Second * 30, Timeout: time.Second * 30,
RootCA: []byte("certificate"),
}, },
}, },
res: res{ res: res{
@ -4674,6 +4680,7 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
[]string{"object"}, []string{"object"},
[]string{"filter"}, []string{"filter"},
time.Second*30, time.Second*30,
[]byte("certificate"),
idp.LDAPAttributes{}, idp.LDAPAttributes{},
idp.Options{}, idp.Options{},
)), )),
@ -4742,6 +4749,7 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
UserObjectClasses: []string{"new object"}, UserObjectClasses: []string{"new object"},
UserFilters: []string{"new filter"}, UserFilters: []string{"new filter"},
Timeout: time.Second * 20, Timeout: time.Second * 20,
RootCA: []byte("certificate"),
LDAPAttributes: idp.LDAPAttributes{ LDAPAttributes: idp.LDAPAttributes{
IDAttribute: "new id", IDAttribute: "new id",
FirstNameAttribute: "new firstName", FirstNameAttribute: "new firstName",

View File

@ -1540,6 +1540,7 @@ func (c *Commands) prepareAddOrgLDAPProvider(a *org.Aggregate, writeModel *OrgLD
provider.UserObjectClasses, provider.UserObjectClasses,
provider.UserFilters, provider.UserFilters,
provider.Timeout, provider.Timeout,
provider.RootCA,
provider.LDAPAttributes, provider.LDAPAttributes,
provider.IDPOptions, provider.IDPOptions,
), ),
@ -1600,6 +1601,7 @@ func (c *Commands) prepareUpdateOrgLDAPProvider(a *org.Aggregate, writeModel *Or
provider.UserObjectClasses, provider.UserObjectClasses,
provider.UserFilters, provider.UserFilters,
provider.Timeout, provider.Timeout,
provider.RootCA,
c.idpConfigEncryption, c.idpConfigEncryption,
provider.LDAPAttributes, provider.LDAPAttributes,
provider.IDPOptions, provider.IDPOptions,

View File

@ -778,6 +778,7 @@ func (wm *OrgLDAPIDPWriteModel) NewChangedEvent(
userObjectClasses []string, userObjectClasses []string,
userFilters []string, userFilters []string,
timeout time.Duration, timeout time.Duration,
rootCA []byte,
secretCrypto crypto.EncryptionAlgorithm, secretCrypto crypto.EncryptionAlgorithm,
attributes idp.LDAPAttributes, attributes idp.LDAPAttributes,
options idp.Options, options idp.Options,
@ -794,6 +795,7 @@ func (wm *OrgLDAPIDPWriteModel) NewChangedEvent(
userObjectClasses, userObjectClasses,
userFilters, userFilters,
timeout, timeout,
rootCA,
secretCrypto, secretCrypto,
attributes, attributes,
options, options,

View File

@ -4328,6 +4328,7 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
[]string{"object"}, []string{"object"},
[]string{"filter"}, []string{"filter"},
time.Second*30, time.Second*30,
nil,
idp.LDAPAttributes{}, idp.LDAPAttributes{},
idp.Options{}, idp.Options{},
), ),
@ -4380,6 +4381,7 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
[]string{"object"}, []string{"object"},
[]string{"filter"}, []string{"filter"},
time.Second*30, time.Second*30,
[]byte("certificate"),
idp.LDAPAttributes{ idp.LDAPAttributes{
IDAttribute: "id", IDAttribute: "id",
FirstNameAttribute: "firstName", FirstNameAttribute: "firstName",
@ -4421,6 +4423,7 @@ func TestCommandSide_AddOrgLDAPIDP(t *testing.T) {
UserObjectClasses: []string{"object"}, UserObjectClasses: []string{"object"},
UserFilters: []string{"filter"}, UserFilters: []string{"filter"},
Timeout: time.Second * 30, Timeout: time.Second * 30,
RootCA: []byte("certificate"),
LDAPAttributes: idp.LDAPAttributes{ LDAPAttributes: idp.LDAPAttributes{
IDAttribute: "id", IDAttribute: "id",
FirstNameAttribute: "firstName", FirstNameAttribute: "firstName",
@ -4706,6 +4709,7 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
[]string{"object"}, []string{"object"},
[]string{"filter"}, []string{"filter"},
time.Second*30, time.Second*30,
[]byte("certificate"),
idp.LDAPAttributes{}, idp.LDAPAttributes{},
idp.Options{}, idp.Options{},
)), )),
@ -4725,6 +4729,7 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
UserFilters: []string{"filter"}, UserFilters: []string{"filter"},
UserBase: "user", UserBase: "user",
Timeout: time.Second * 30, Timeout: time.Second * 30,
RootCA: []byte("certificate"),
}, },
}, },
res: res{ res: res{
@ -4754,6 +4759,7 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
[]string{"object"}, []string{"object"},
[]string{"filter"}, []string{"filter"},
time.Second*30, time.Second*30,
[]byte("certificate"),
idp.LDAPAttributes{}, idp.LDAPAttributes{},
idp.Options{}, idp.Options{},
)), )),
@ -4823,6 +4829,7 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
UserObjectClasses: []string{"new object"}, UserObjectClasses: []string{"new object"},
UserFilters: []string{"new filter"}, UserFilters: []string{"new filter"},
Timeout: time.Second * 20, Timeout: time.Second * 20,
RootCA: []byte("certificate"),
LDAPAttributes: idp.LDAPAttributes{ LDAPAttributes: idp.LDAPAttributes{
IDAttribute: "new id", IDAttribute: "new id",
FirstNameAttribute: "new firstName", FirstNameAttribute: "new firstName",

View File

@ -23,6 +23,7 @@ type Provider struct {
userObjectClasses []string userObjectClasses []string
userFilters []string userFilters []string
timeout time.Duration timeout time.Duration
rootCA []byte
loginUrl string loginUrl string
@ -185,6 +186,7 @@ func New(
userObjectClasses []string, userObjectClasses []string,
userFilters []string, userFilters []string,
timeout time.Duration, timeout time.Duration,
rootCA []byte,
loginUrl string, loginUrl string,
options ...ProviderOpts, options ...ProviderOpts,
) *Provider { ) *Provider {
@ -199,6 +201,7 @@ func New(
userObjectClasses: userObjectClasses, userObjectClasses: userObjectClasses,
userFilters: userFilters, userFilters: userFilters,
timeout: timeout, timeout: timeout,
rootCA: rootCA,
loginUrl: loginUrl, loginUrl: loginUrl,
} }
for _, option := range options { for _, option := range options {

View File

@ -18,11 +18,13 @@ func TestProvider_Options(t *testing.T) {
userObjectClasses []string userObjectClasses []string
userFilters []string userFilters []string
timeout time.Duration timeout time.Duration
rootCA []byte
loginUrl string loginUrl string
opts []ProviderOpts opts []ProviderOpts
} }
type want struct { type want struct {
name string name string
rootCA []byte
startTls bool startTls bool
linkingAllowed bool linkingAllowed bool
creationAllowed bool creationAllowed bool
@ -114,6 +116,7 @@ func TestProvider_Options(t *testing.T) {
userObjectClasses: []string{"object"}, userObjectClasses: []string{"object"},
userFilters: []string{"filter"}, userFilters: []string{"filter"},
timeout: 30 * time.Second, timeout: 30 * time.Second,
rootCA: []byte("certificate"),
loginUrl: "url", loginUrl: "url",
opts: []ProviderOpts{ opts: []ProviderOpts{
WithoutStartTLS(), WithoutStartTLS(),
@ -138,6 +141,7 @@ func TestProvider_Options(t *testing.T) {
}, },
want: want{ want: want{
name: "ldap", name: "ldap",
rootCA: []byte("certificate"),
startTls: false, startTls: false,
linkingAllowed: true, linkingAllowed: true,
creationAllowed: true, creationAllowed: true,
@ -172,11 +176,13 @@ func TestProvider_Options(t *testing.T) {
tt.fields.userObjectClasses, tt.fields.userObjectClasses,
tt.fields.userFilters, tt.fields.userFilters,
tt.fields.timeout, tt.fields.timeout,
tt.fields.rootCA,
tt.fields.loginUrl, tt.fields.loginUrl,
tt.fields.opts..., tt.fields.opts...,
) )
a.Equal(tt.want.name, provider.Name()) a.Equal(tt.want.name, provider.Name())
a.Equal(tt.want.rootCA, provider.rootCA)
a.Equal(tt.want.startTls, provider.startTLS) a.Equal(tt.want.startTls, provider.startTLS)
a.Equal(tt.want.linkingAllowed, provider.IsLinkingAllowed()) a.Equal(tt.want.linkingAllowed, provider.IsLinkingAllowed())
a.Equal(tt.want.creationAllowed, provider.IsCreationAllowed()) a.Equal(tt.want.creationAllowed, provider.IsCreationAllowed())

View File

@ -3,6 +3,7 @@ package ldap
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"crypto/x509"
"encoding/base64" "encoding/base64"
"errors" "errors"
"net" "net"
@ -21,6 +22,7 @@ import (
var ErrNoSingleUser = errors.New("user does not exist or too many entries returned") var ErrNoSingleUser = errors.New("user does not exist or too many entries returned")
var ErrFailedLogin = errors.New("user failed to login") var ErrFailedLogin = errors.New("user failed to login")
var ErrUnableToAppendRootCA = errors.New("unable to append rootCA")
var _ idp.Session = (*Session)(nil) var _ idp.Session = (*Session)(nil)
@ -49,7 +51,9 @@ func (s *Session) FetchUser(_ context.Context) (_ idp.User, err error) {
s.Provider.userObjectClasses, s.Provider.userObjectClasses,
s.Provider.userFilters, s.Provider.userFilters,
s.User, s.User,
s.Password, s.Provider.timeout) s.Password,
s.Provider.timeout,
s.Provider.rootCA)
// If there were invalid credentials or multiple users with the credentials cancel process // If there were invalid credentials or multiple users with the credentials cancel process
if err != nil && (errors.Is(err, ErrFailedLogin) || errors.Is(err, ErrNoSingleUser)) { if err != nil && (errors.Is(err, ErrFailedLogin) || errors.Is(err, ErrNoSingleUser)) {
return nil, err return nil, err
@ -94,8 +98,9 @@ func tryBind(
username string, username string,
password string, password string,
timeout time.Duration, timeout time.Duration,
rootCA []byte,
) (*ldap.Entry, error) { ) (*ldap.Entry, error) {
conn, err := getConnection(server, startTLS, timeout) conn, err := getConnection(server, startTLS, timeout, rootCA)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -114,6 +119,7 @@ func tryBind(
username, username,
password, password,
timeout, timeout,
rootCA,
) )
} }
@ -121,21 +127,37 @@ func getConnection(
server string, server string,
startTLS bool, startTLS bool,
timeout time.Duration, timeout time.Duration,
rootCA []byte,
) (*ldap.Conn, error) { ) (*ldap.Conn, error) {
if timeout == 0 { if timeout == 0 {
timeout = ldap.DefaultTimeout timeout = ldap.DefaultTimeout
} }
conn, err := ldap.DialURL(server, ldap.DialWithDialer(&net.Dialer{Timeout: timeout})) dialer := make([]ldap.DialOpt, 1, 2)
if err != nil { dialer[0] = ldap.DialWithDialer(&net.Dialer{Timeout: timeout})
return nil, err
}
u, err := url.Parse(server) u, err := url.Parse(server)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if u.Scheme == "ldaps" && startTLS {
if u.Scheme == "ldaps" && len(rootCA) > 0 {
rootCAs := x509.NewCertPool()
if ok := rootCAs.AppendCertsFromPEM(rootCA); !ok {
return nil, ErrUnableToAppendRootCA
}
dialer = append(dialer, ldap.DialWithTLSConfig(&tls.Config{
RootCAs: rootCAs,
}))
}
conn, err := ldap.DialURL(server, dialer...)
if err != nil {
return nil, err
}
if u.Scheme == "ldap" && startTLS {
err = conn.StartTLS(&tls.Config{ServerName: u.Host}) err = conn.StartTLS(&tls.Config{ServerName: u.Host})
if err != nil { if err != nil {
return nil, err return nil, err
@ -153,6 +175,7 @@ func trySearchAndUserBind(
username string, username string,
password string, password string,
timeout time.Duration, timeout time.Duration,
rootCA []byte,
) (*ldap.Entry, error) { ) (*ldap.Entry, error) {
searchQuery := queriesAndToSearchQuery( searchQuery := queriesAndToSearchQuery(
objectClassesToSearchQuery(objectClasses), objectClassesToSearchQuery(objectClasses),

View File

@ -142,6 +142,7 @@ type LDAPIDPTemplate struct {
UserObjectClasses []string UserObjectClasses []string
UserFilters []string UserFilters []string
Timeout time.Duration Timeout time.Duration
RootCA []byte
idp.LDAPAttributes idp.LDAPAttributes
} }
@ -580,6 +581,10 @@ var (
name: projection.LDAPTimeoutCol, name: projection.LDAPTimeoutCol,
table: ldapIdpTemplateTable, table: ldapIdpTemplateTable,
} }
LDAPRootCACol = Column{
name: projection.LDAPRootCACol,
table: ldapIdpTemplateTable,
}
LDAPIDAttributeCol = Column{ LDAPIDAttributeCol = Column{
name: projection.LDAPIDAttributeCol, name: projection.LDAPIDAttributeCol,
table: ldapIdpTemplateTable, table: ldapIdpTemplateTable,
@ -943,6 +948,7 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
LDAPUserObjectClassesCol.identifier(), LDAPUserObjectClassesCol.identifier(),
LDAPUserFiltersCol.identifier(), LDAPUserFiltersCol.identifier(),
LDAPTimeoutCol.identifier(), LDAPTimeoutCol.identifier(),
LDAPRootCACol.identifier(),
LDAPIDAttributeCol.identifier(), LDAPIDAttributeCol.identifier(),
LDAPFirstNameAttributeCol.identifier(), LDAPFirstNameAttributeCol.identifier(),
LDAPLastNameAttributeCol.identifier(), LDAPLastNameAttributeCol.identifier(),
@ -1059,6 +1065,7 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
ldapUserObjectClasses := database.TextArray[string]{} ldapUserObjectClasses := database.TextArray[string]{}
ldapUserFilters := database.TextArray[string]{} ldapUserFilters := database.TextArray[string]{}
ldapTimeout := sql.NullInt64{} ldapTimeout := sql.NullInt64{}
var ldapRootCA []byte
ldapIDAttribute := sql.NullString{} ldapIDAttribute := sql.NullString{}
ldapFirstNameAttribute := sql.NullString{} ldapFirstNameAttribute := sql.NullString{}
ldapLastNameAttribute := sql.NullString{} ldapLastNameAttribute := sql.NullString{}
@ -1173,6 +1180,7 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
&ldapUserObjectClasses, &ldapUserObjectClasses,
&ldapUserFilters, &ldapUserFilters,
&ldapTimeout, &ldapTimeout,
&ldapRootCA,
&ldapIDAttribute, &ldapIDAttribute,
&ldapFirstNameAttribute, &ldapFirstNameAttribute,
&ldapLastNameAttribute, &ldapLastNameAttribute,
@ -1312,6 +1320,7 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
UserObjectClasses: ldapUserObjectClasses, UserObjectClasses: ldapUserObjectClasses,
UserFilters: ldapUserFilters, UserFilters: ldapUserFilters,
Timeout: time.Duration(ldapTimeout.Int64), Timeout: time.Duration(ldapTimeout.Int64),
RootCA: ldapRootCA,
LDAPAttributes: idp.LDAPAttributes{ LDAPAttributes: idp.LDAPAttributes{
IDAttribute: ldapIDAttribute.String, IDAttribute: ldapIDAttribute.String,
FirstNameAttribute: ldapFirstNameAttribute.String, FirstNameAttribute: ldapFirstNameAttribute.String,
@ -1438,6 +1447,7 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
LDAPUserObjectClassesCol.identifier(), LDAPUserObjectClassesCol.identifier(),
LDAPUserFiltersCol.identifier(), LDAPUserFiltersCol.identifier(),
LDAPTimeoutCol.identifier(), LDAPTimeoutCol.identifier(),
LDAPRootCACol.identifier(),
LDAPIDAttributeCol.identifier(), LDAPIDAttributeCol.identifier(),
LDAPFirstNameAttributeCol.identifier(), LDAPFirstNameAttributeCol.identifier(),
LDAPLastNameAttributeCol.identifier(), LDAPLastNameAttributeCol.identifier(),
@ -1559,6 +1569,7 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
ldapUserObjectClasses := database.TextArray[string]{} ldapUserObjectClasses := database.TextArray[string]{}
ldapUserFilters := database.TextArray[string]{} ldapUserFilters := database.TextArray[string]{}
ldapTimeout := sql.NullInt64{} ldapTimeout := sql.NullInt64{}
var ldapRootCA []byte
ldapIDAttribute := sql.NullString{} ldapIDAttribute := sql.NullString{}
ldapFirstNameAttribute := sql.NullString{} ldapFirstNameAttribute := sql.NullString{}
ldapLastNameAttribute := sql.NullString{} ldapLastNameAttribute := sql.NullString{}
@ -1673,6 +1684,7 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
&ldapUserObjectClasses, &ldapUserObjectClasses,
&ldapUserFilters, &ldapUserFilters,
&ldapTimeout, &ldapTimeout,
&ldapRootCA,
&ldapIDAttribute, &ldapIDAttribute,
&ldapFirstNameAttribute, &ldapFirstNameAttribute,
&ldapLastNameAttribute, &ldapLastNameAttribute,
@ -1811,6 +1823,7 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
UserObjectClasses: ldapUserObjectClasses, UserObjectClasses: ldapUserObjectClasses,
UserFilters: ldapUserFilters, UserFilters: ldapUserFilters,
Timeout: time.Duration(ldapTimeout.Int64), Timeout: time.Duration(ldapTimeout.Int64),
RootCA: ldapRootCA,
LDAPAttributes: idp.LDAPAttributes{ LDAPAttributes: idp.LDAPAttributes{
IDAttribute: ldapIDAttribute.String, IDAttribute: ldapIDAttribute.String,
FirstNameAttribute: ldapFirstNameAttribute.String, FirstNameAttribute: ldapFirstNameAttribute.String,

View File

@ -98,29 +98,30 @@ var (
` projections.idp_templates6_saml.name_id_format,` + ` projections.idp_templates6_saml.name_id_format,` +
` projections.idp_templates6_saml.transient_mapping_attribute_name,` + ` projections.idp_templates6_saml.transient_mapping_attribute_name,` +
// ldap // ldap
` projections.idp_templates6_ldap2.idp_id,` + ` projections.idp_templates6_ldap3.idp_id,` +
` projections.idp_templates6_ldap2.servers,` + ` projections.idp_templates6_ldap3.servers,` +
` projections.idp_templates6_ldap2.start_tls,` + ` projections.idp_templates6_ldap3.start_tls,` +
` projections.idp_templates6_ldap2.base_dn,` + ` projections.idp_templates6_ldap3.base_dn,` +
` projections.idp_templates6_ldap2.bind_dn,` + ` projections.idp_templates6_ldap3.bind_dn,` +
` projections.idp_templates6_ldap2.bind_password,` + ` projections.idp_templates6_ldap3.bind_password,` +
` projections.idp_templates6_ldap2.user_base,` + ` projections.idp_templates6_ldap3.user_base,` +
` projections.idp_templates6_ldap2.user_object_classes,` + ` projections.idp_templates6_ldap3.user_object_classes,` +
` projections.idp_templates6_ldap2.user_filters,` + ` projections.idp_templates6_ldap3.user_filters,` +
` projections.idp_templates6_ldap2.timeout,` + ` projections.idp_templates6_ldap3.timeout,` +
` projections.idp_templates6_ldap2.id_attribute,` + ` projections.idp_templates6_ldap3.rootCA,` +
` projections.idp_templates6_ldap2.first_name_attribute,` + ` projections.idp_templates6_ldap3.id_attribute,` +
` projections.idp_templates6_ldap2.last_name_attribute,` + ` projections.idp_templates6_ldap3.first_name_attribute,` +
` projections.idp_templates6_ldap2.display_name_attribute,` + ` projections.idp_templates6_ldap3.last_name_attribute,` +
` projections.idp_templates6_ldap2.nick_name_attribute,` + ` projections.idp_templates6_ldap3.display_name_attribute,` +
` projections.idp_templates6_ldap2.preferred_username_attribute,` + ` projections.idp_templates6_ldap3.nick_name_attribute,` +
` projections.idp_templates6_ldap2.email_attribute,` + ` projections.idp_templates6_ldap3.preferred_username_attribute,` +
` projections.idp_templates6_ldap2.email_verified,` + ` projections.idp_templates6_ldap3.email_attribute,` +
` projections.idp_templates6_ldap2.phone_attribute,` + ` projections.idp_templates6_ldap3.email_verified,` +
` projections.idp_templates6_ldap2.phone_verified_attribute,` + ` projections.idp_templates6_ldap3.phone_attribute,` +
` projections.idp_templates6_ldap2.preferred_language_attribute,` + ` projections.idp_templates6_ldap3.phone_verified_attribute,` +
` projections.idp_templates6_ldap2.avatar_url_attribute,` + ` projections.idp_templates6_ldap3.preferred_language_attribute,` +
` projections.idp_templates6_ldap2.profile_attribute,` + ` projections.idp_templates6_ldap3.avatar_url_attribute,` +
` projections.idp_templates6_ldap3.profile_attribute,` +
// apple // apple
` projections.idp_templates6_apple.idp_id,` + ` projections.idp_templates6_apple.idp_id,` +
` projections.idp_templates6_apple.client_id,` + ` projections.idp_templates6_apple.client_id,` +
@ -139,7 +140,7 @@ var (
` LEFT JOIN projections.idp_templates6_gitlab_self_hosted ON projections.idp_templates6.id = projections.idp_templates6_gitlab_self_hosted.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_gitlab_self_hosted.instance_id` + ` LEFT JOIN projections.idp_templates6_gitlab_self_hosted ON projections.idp_templates6.id = projections.idp_templates6_gitlab_self_hosted.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_gitlab_self_hosted.instance_id` +
` LEFT JOIN projections.idp_templates6_google ON projections.idp_templates6.id = projections.idp_templates6_google.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_google.instance_id` + ` LEFT JOIN projections.idp_templates6_google ON projections.idp_templates6.id = projections.idp_templates6_google.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_google.instance_id` +
` LEFT JOIN projections.idp_templates6_saml ON projections.idp_templates6.id = projections.idp_templates6_saml.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_saml.instance_id` + ` LEFT JOIN projections.idp_templates6_saml ON projections.idp_templates6.id = projections.idp_templates6_saml.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_saml.instance_id` +
` LEFT JOIN projections.idp_templates6_ldap2 ON projections.idp_templates6.id = projections.idp_templates6_ldap2.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_ldap2.instance_id` + ` LEFT JOIN projections.idp_templates6_ldap3 ON projections.idp_templates6.id = projections.idp_templates6_ldap3.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_ldap3.instance_id` +
` LEFT JOIN projections.idp_templates6_apple ON projections.idp_templates6.id = projections.idp_templates6_apple.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_apple.instance_id` + ` LEFT JOIN projections.idp_templates6_apple ON projections.idp_templates6.id = projections.idp_templates6_apple.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_apple.instance_id` +
` AS OF SYSTEM TIME '-1 ms'` ` AS OF SYSTEM TIME '-1 ms'`
idpTemplateCols = []string{ idpTemplateCols = []string{
@ -235,6 +236,7 @@ var (
"user_object_classes", "user_object_classes",
"user_filters", "user_filters",
"timeout", "timeout",
"rootCA",
"id_attribute", "id_attribute",
"first_name_attribute", "first_name_attribute",
"last_name_attribute", "last_name_attribute",
@ -338,29 +340,30 @@ var (
` projections.idp_templates6_saml.name_id_format,` + ` projections.idp_templates6_saml.name_id_format,` +
` projections.idp_templates6_saml.transient_mapping_attribute_name,` + ` projections.idp_templates6_saml.transient_mapping_attribute_name,` +
// ldap // ldap
` projections.idp_templates6_ldap2.idp_id,` + ` projections.idp_templates6_ldap3.idp_id,` +
` projections.idp_templates6_ldap2.servers,` + ` projections.idp_templates6_ldap3.servers,` +
` projections.idp_templates6_ldap2.start_tls,` + ` projections.idp_templates6_ldap3.start_tls,` +
` projections.idp_templates6_ldap2.base_dn,` + ` projections.idp_templates6_ldap3.base_dn,` +
` projections.idp_templates6_ldap2.bind_dn,` + ` projections.idp_templates6_ldap3.bind_dn,` +
` projections.idp_templates6_ldap2.bind_password,` + ` projections.idp_templates6_ldap3.bind_password,` +
` projections.idp_templates6_ldap2.user_base,` + ` projections.idp_templates6_ldap3.user_base,` +
` projections.idp_templates6_ldap2.user_object_classes,` + ` projections.idp_templates6_ldap3.user_object_classes,` +
` projections.idp_templates6_ldap2.user_filters,` + ` projections.idp_templates6_ldap3.user_filters,` +
` projections.idp_templates6_ldap2.timeout,` + ` projections.idp_templates6_ldap3.timeout,` +
` projections.idp_templates6_ldap2.id_attribute,` + ` projections.idp_templates6_ldap3.rootCA,` +
` projections.idp_templates6_ldap2.first_name_attribute,` + ` projections.idp_templates6_ldap3.id_attribute,` +
` projections.idp_templates6_ldap2.last_name_attribute,` + ` projections.idp_templates6_ldap3.first_name_attribute,` +
` projections.idp_templates6_ldap2.display_name_attribute,` + ` projections.idp_templates6_ldap3.last_name_attribute,` +
` projections.idp_templates6_ldap2.nick_name_attribute,` + ` projections.idp_templates6_ldap3.display_name_attribute,` +
` projections.idp_templates6_ldap2.preferred_username_attribute,` + ` projections.idp_templates6_ldap3.nick_name_attribute,` +
` projections.idp_templates6_ldap2.email_attribute,` + ` projections.idp_templates6_ldap3.preferred_username_attribute,` +
` projections.idp_templates6_ldap2.email_verified,` + ` projections.idp_templates6_ldap3.email_attribute,` +
` projections.idp_templates6_ldap2.phone_attribute,` + ` projections.idp_templates6_ldap3.email_verified,` +
` projections.idp_templates6_ldap2.phone_verified_attribute,` + ` projections.idp_templates6_ldap3.phone_attribute,` +
` projections.idp_templates6_ldap2.preferred_language_attribute,` + ` projections.idp_templates6_ldap3.phone_verified_attribute,` +
` projections.idp_templates6_ldap2.avatar_url_attribute,` + ` projections.idp_templates6_ldap3.preferred_language_attribute,` +
` projections.idp_templates6_ldap2.profile_attribute,` + ` projections.idp_templates6_ldap3.avatar_url_attribute,` +
` projections.idp_templates6_ldap3.profile_attribute,` +
// apple // apple
` projections.idp_templates6_apple.idp_id,` + ` projections.idp_templates6_apple.idp_id,` +
` projections.idp_templates6_apple.client_id,` + ` projections.idp_templates6_apple.client_id,` +
@ -380,7 +383,7 @@ var (
` LEFT JOIN projections.idp_templates6_gitlab_self_hosted ON projections.idp_templates6.id = projections.idp_templates6_gitlab_self_hosted.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_gitlab_self_hosted.instance_id` + ` LEFT JOIN projections.idp_templates6_gitlab_self_hosted ON projections.idp_templates6.id = projections.idp_templates6_gitlab_self_hosted.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_gitlab_self_hosted.instance_id` +
` LEFT JOIN projections.idp_templates6_google ON projections.idp_templates6.id = projections.idp_templates6_google.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_google.instance_id` + ` LEFT JOIN projections.idp_templates6_google ON projections.idp_templates6.id = projections.idp_templates6_google.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_google.instance_id` +
` LEFT JOIN projections.idp_templates6_saml ON projections.idp_templates6.id = projections.idp_templates6_saml.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_saml.instance_id` + ` LEFT JOIN projections.idp_templates6_saml ON projections.idp_templates6.id = projections.idp_templates6_saml.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_saml.instance_id` +
` LEFT JOIN projections.idp_templates6_ldap2 ON projections.idp_templates6.id = projections.idp_templates6_ldap2.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_ldap2.instance_id` + ` LEFT JOIN projections.idp_templates6_ldap3 ON projections.idp_templates6.id = projections.idp_templates6_ldap3.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_ldap3.instance_id` +
` LEFT JOIN projections.idp_templates6_apple ON projections.idp_templates6.id = projections.idp_templates6_apple.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_apple.instance_id` + ` LEFT JOIN projections.idp_templates6_apple ON projections.idp_templates6.id = projections.idp_templates6_apple.idp_id AND projections.idp_templates6.instance_id = projections.idp_templates6_apple.instance_id` +
` AS OF SYSTEM TIME '-1 ms'` ` AS OF SYSTEM TIME '-1 ms'`
idpTemplatesCols = []string{ idpTemplatesCols = []string{
@ -476,6 +479,7 @@ var (
"user_object_classes", "user_object_classes",
"user_filters", "user_filters",
"timeout", "timeout",
"rootCA",
"id_attribute", "id_attribute",
"first_name_attribute", "first_name_attribute",
"last_name_attribute", "last_name_attribute",
@ -642,6 +646,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// apple // apple
nil, nil,
nil, nil,
@ -792,6 +797,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// apple // apple
nil, nil,
nil, nil,
@ -940,6 +946,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// apple // apple
nil, nil,
nil, nil,
@ -1087,6 +1094,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// apple // apple
nil, nil,
nil, nil,
@ -1233,6 +1241,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// apple // apple
nil, nil,
nil, nil,
@ -1379,6 +1388,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// apple // apple
nil, nil,
nil, nil,
@ -1526,6 +1536,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// apple // apple
nil, nil,
nil, nil,
@ -1672,6 +1683,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// apple // apple
nil, nil,
nil, nil,
@ -1809,6 +1821,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
database.TextArray[string]{"object"}, database.TextArray[string]{"object"},
database.TextArray[string]{"filter"}, database.TextArray[string]{"filter"},
time.Duration(30000000000), time.Duration(30000000000),
[]byte("certificate"),
"id", "id",
"first", "first",
"last", "last",
@ -1857,6 +1870,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
UserObjectClasses: []string{"object"}, UserObjectClasses: []string{"object"},
UserFilters: []string{"filter"}, UserFilters: []string{"filter"},
Timeout: time.Duration(30000000000), Timeout: time.Duration(30000000000),
RootCA: []byte("certificate"),
LDAPAttributes: idp.LDAPAttributes{ LDAPAttributes: idp.LDAPAttributes{
IDAttribute: "id", IDAttribute: "id",
FirstNameAttribute: "first", FirstNameAttribute: "first",
@ -1988,6 +2002,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// apple // apple
"idp-id", "idp-id",
"client_id", "client_id",
@ -2136,6 +2151,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// apple // apple
nil, nil,
nil, nil,
@ -2299,6 +2315,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
database.TextArray[string]{"object"}, database.TextArray[string]{"object"},
database.TextArray[string]{"filter"}, database.TextArray[string]{"filter"},
time.Duration(30000000000), time.Duration(30000000000),
[]byte("certificate"),
"id", "id",
"first", "first",
"last", "last",
@ -2353,6 +2370,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
UserObjectClasses: []string{"object"}, UserObjectClasses: []string{"object"},
UserFilters: []string{"filter"}, UserFilters: []string{"filter"},
Timeout: time.Duration(30000000000), Timeout: time.Duration(30000000000),
RootCA: []byte("certificate"),
LDAPAttributes: idp.LDAPAttributes{ LDAPAttributes: idp.LDAPAttributes{
IDAttribute: "id", IDAttribute: "id",
FirstNameAttribute: "first", FirstNameAttribute: "first",
@ -2487,6 +2505,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// apple // apple
nil, nil,
nil, nil,
@ -2623,6 +2642,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
database.TextArray[string]{"object"}, database.TextArray[string]{"object"},
database.TextArray[string]{"filter"}, database.TextArray[string]{"filter"},
time.Duration(30000000000), time.Duration(30000000000),
[]byte("certificate"),
"id", "id",
"first", "first",
"last", "last",
@ -2750,6 +2770,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// apple // apple
nil, nil,
nil, nil,
@ -2864,6 +2885,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// apple // apple
nil, nil,
nil, nil,
@ -2978,6 +3000,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// apple // apple
nil, nil,
nil, nil,
@ -3092,6 +3115,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// apple // apple
nil, nil,
nil, nil,
@ -3206,6 +3230,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
nil,
// apple // apple
nil, nil,
nil, nil,
@ -3247,6 +3272,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
UserObjectClasses: []string{"object"}, UserObjectClasses: []string{"object"},
UserFilters: []string{"filter"}, UserFilters: []string{"filter"},
Timeout: time.Duration(30000000000), Timeout: time.Duration(30000000000),
RootCA: []byte("certificate"),
LDAPAttributes: idp.LDAPAttributes{ LDAPAttributes: idp.LDAPAttributes{
IDAttribute: "id", IDAttribute: "id",
FirstNameAttribute: "first", FirstNameAttribute: "first",

View File

@ -40,7 +40,7 @@ const (
IDPTemplateGitLabSuffix = "gitlab" IDPTemplateGitLabSuffix = "gitlab"
IDPTemplateGitLabSelfHostedSuffix = "gitlab_self_hosted" IDPTemplateGitLabSelfHostedSuffix = "gitlab_self_hosted"
IDPTemplateGoogleSuffix = "google" IDPTemplateGoogleSuffix = "google"
IDPTemplateLDAPSuffix = "ldap2" IDPTemplateLDAPSuffix = "ldap3"
IDPTemplateAppleSuffix = "apple" IDPTemplateAppleSuffix = "apple"
IDPTemplateSAMLSuffix = "saml" IDPTemplateSAMLSuffix = "saml"
@ -139,6 +139,7 @@ const (
LDAPUserObjectClassesCol = "user_object_classes" LDAPUserObjectClassesCol = "user_object_classes"
LDAPUserFiltersCol = "user_filters" LDAPUserFiltersCol = "user_filters"
LDAPTimeoutCol = "timeout" LDAPTimeoutCol = "timeout"
LDAPRootCACol = "rootCA"
LDAPIDAttributeCol = "id_attribute" LDAPIDAttributeCol = "id_attribute"
LDAPFirstNameAttributeCol = "first_name_attribute" LDAPFirstNameAttributeCol = "first_name_attribute"
LDAPLastNameAttributeCol = "last_name_attribute" LDAPLastNameAttributeCol = "last_name_attribute"
@ -330,6 +331,7 @@ func (*idpTemplateProjection) Init() *old_handler.Check {
handler.NewColumn(LDAPUserObjectClassesCol, handler.ColumnTypeTextArray), handler.NewColumn(LDAPUserObjectClassesCol, handler.ColumnTypeTextArray),
handler.NewColumn(LDAPUserFiltersCol, handler.ColumnTypeTextArray), handler.NewColumn(LDAPUserFiltersCol, handler.ColumnTypeTextArray),
handler.NewColumn(LDAPTimeoutCol, handler.ColumnTypeInt64), handler.NewColumn(LDAPTimeoutCol, handler.ColumnTypeInt64),
handler.NewColumn(LDAPRootCACol, handler.ColumnTypeBytes, handler.Nullable()),
handler.NewColumn(LDAPIDAttributeCol, handler.ColumnTypeText, handler.Nullable()), handler.NewColumn(LDAPIDAttributeCol, handler.ColumnTypeText, handler.Nullable()),
handler.NewColumn(LDAPFirstNameAttributeCol, handler.ColumnTypeText, handler.Nullable()), handler.NewColumn(LDAPFirstNameAttributeCol, handler.ColumnTypeText, handler.Nullable()),
handler.NewColumn(LDAPLastNameAttributeCol, handler.ColumnTypeText, handler.Nullable()), handler.NewColumn(LDAPLastNameAttributeCol, handler.ColumnTypeText, handler.Nullable()),
@ -1896,6 +1898,7 @@ func (p *idpTemplateProjection) reduceLDAPIDPAdded(event eventstore.Event) (*han
handler.NewCol(LDAPUserObjectClassesCol, database.TextArray[string](idpEvent.UserObjectClasses)), handler.NewCol(LDAPUserObjectClassesCol, database.TextArray[string](idpEvent.UserObjectClasses)),
handler.NewCol(LDAPUserFiltersCol, database.TextArray[string](idpEvent.UserFilters)), handler.NewCol(LDAPUserFiltersCol, database.TextArray[string](idpEvent.UserFilters)),
handler.NewCol(LDAPTimeoutCol, idpEvent.Timeout), handler.NewCol(LDAPTimeoutCol, idpEvent.Timeout),
handler.NewCol(LDAPRootCACol, idpEvent.RootCA),
handler.NewCol(LDAPIDAttributeCol, idpEvent.IDAttribute), handler.NewCol(LDAPIDAttributeCol, idpEvent.IDAttribute),
handler.NewCol(LDAPFirstNameAttributeCol, idpEvent.FirstNameAttribute), handler.NewCol(LDAPFirstNameAttributeCol, idpEvent.FirstNameAttribute),
handler.NewCol(LDAPLastNameAttributeCol, idpEvent.LastNameAttribute), handler.NewCol(LDAPLastNameAttributeCol, idpEvent.LastNameAttribute),
@ -2421,6 +2424,9 @@ func reduceLDAPIDPChangedColumns(idpEvent idp.LDAPIDPChangedEvent) []handler.Col
if idpEvent.Timeout != nil { if idpEvent.Timeout != nil {
ldapCols = append(ldapCols, handler.NewCol(LDAPTimeoutCol, *idpEvent.Timeout)) ldapCols = append(ldapCols, handler.NewCol(LDAPTimeoutCol, *idpEvent.Timeout))
} }
if idpEvent.RootCA != nil {
ldapCols = append(ldapCols, handler.NewCol(LDAPRootCACol, idpEvent.RootCA))
}
if idpEvent.IDAttribute != nil { if idpEvent.IDAttribute != nil {
ldapCols = append(ldapCols, handler.NewCol(LDAPIDAttributeCol, *idpEvent.IDAttribute)) ldapCols = append(ldapCols, handler.NewCol(LDAPIDAttributeCol, *idpEvent.IDAttribute))
} }

View File

@ -2117,6 +2117,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
"userObjectClasses": ["object"], "userObjectClasses": ["object"],
"userFilters": ["filter"], "userFilters": ["filter"],
"timeout": 30000000000, "timeout": 30000000000,
"rootcA": `+stringToJSONByte("certificate")+`,
"idAttribute": "id", "idAttribute": "id",
"firstNameAttribute": "first", "firstNameAttribute": "first",
"lastNameAttribute": "last", "lastNameAttribute": "last",
@ -2165,7 +2166,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.idp_templates6_ldap2 (idp_id, instance_id, servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)", expectedStmt: "INSERT INTO projections.idp_templates6_ldap3 (idp_id, instance_id, servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, rootCA, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"idp-id", "idp-id",
"instance-id", "instance-id",
@ -2178,6 +2179,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
database.TextArray[string]{"object"}, database.TextArray[string]{"object"},
database.TextArray[string]{"filter"}, database.TextArray[string]{"filter"},
time.Duration(30000000000), time.Duration(30000000000),
[]byte("certificate"),
"id", "id",
"first", "first",
"last", "last",
@ -2220,6 +2222,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
"userObjectClasses": ["object"], "userObjectClasses": ["object"],
"userFilters": ["filter"], "userFilters": ["filter"],
"timeout": 30000000000, "timeout": 30000000000,
"rootcA": `+stringToJSONByte("certificate")+`,
"idAttribute": "id", "idAttribute": "id",
"firstNameAttribute": "first", "firstNameAttribute": "first",
"lastNameAttribute": "last", "lastNameAttribute": "last",
@ -2268,7 +2271,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.idp_templates6_ldap2 (idp_id, instance_id, servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24)", expectedStmt: "INSERT INTO projections.idp_templates6_ldap3 (idp_id, instance_id, servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, rootCA, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"idp-id", "idp-id",
"instance-id", "instance-id",
@ -2281,6 +2284,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
database.TextArray[string]{"object"}, database.TextArray[string]{"object"},
database.TextArray[string]{"filter"}, database.TextArray[string]{"filter"},
time.Duration(30000000000), time.Duration(30000000000),
[]byte("certificate"),
"id", "id",
"first", "first",
"last", "last",
@ -2331,7 +2335,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.idp_templates6_ldap2 SET base_dn = $1 WHERE (idp_id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.idp_templates6_ldap3 SET base_dn = $1 WHERE (idp_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"basedn", "basedn",
"idp-id", "idp-id",
@ -2365,6 +2369,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
"userObjectClasses": ["object"], "userObjectClasses": ["object"],
"userFilters": ["filter"], "userFilters": ["filter"],
"timeout": 30000000000, "timeout": 30000000000,
"rootcA": `+stringToJSONByte("certificate")+`,
"idAttribute": "id", "idAttribute": "id",
"firstNameAttribute": "first", "firstNameAttribute": "first",
"lastNameAttribute": "last", "lastNameAttribute": "last",
@ -2408,7 +2413,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.idp_templates6_ldap2 SET (servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22) WHERE (idp_id = $23) AND (instance_id = $24)", expectedStmt: "UPDATE projections.idp_templates6_ldap3 SET (servers, start_tls, base_dn, bind_dn, bind_password, user_base, user_object_classes, user_filters, timeout, rootCA, id_attribute, first_name_attribute, last_name_attribute, display_name_attribute, nick_name_attribute, preferred_username_attribute, email_attribute, email_verified, phone_attribute, phone_verified_attribute, preferred_language_attribute, avatar_url_attribute, profile_attribute) = ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23) WHERE (idp_id = $24) AND (instance_id = $25)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
database.TextArray[string]{"server"}, database.TextArray[string]{"server"},
false, false,
@ -2419,6 +2424,7 @@ func TestIDPTemplateProjection_reducesLDAP(t *testing.T) {
database.TextArray[string]{"object"}, database.TextArray[string]{"object"},
database.TextArray[string]{"filter"}, database.TextArray[string]{"filter"},
time.Duration(30000000000), time.Duration(30000000000),
[]byte("certificate"),
"id", "id",
"first", "first",
"last", "last",

View File

@ -22,6 +22,7 @@ type LDAPIDPAddedEvent struct {
UserObjectClasses []string `json:"userObjectClasses"` UserObjectClasses []string `json:"userObjectClasses"`
UserFilters []string `json:"userFilters"` UserFilters []string `json:"userFilters"`
Timeout time.Duration `json:"timeout"` Timeout time.Duration `json:"timeout"`
RootCA []byte `json:"rootCA"`
LDAPAttributes LDAPAttributes
Options Options
@ -142,6 +143,7 @@ func NewLDAPIDPAddedEvent(
userObjectClasses []string, userObjectClasses []string,
userFilters []string, userFilters []string,
timeout time.Duration, timeout time.Duration,
rootCA []byte,
attributes LDAPAttributes, attributes LDAPAttributes,
options Options, options Options,
) *LDAPIDPAddedEvent { ) *LDAPIDPAddedEvent {
@ -158,6 +160,7 @@ func NewLDAPIDPAddedEvent(
UserObjectClasses: userObjectClasses, UserObjectClasses: userObjectClasses,
UserFilters: userFilters, UserFilters: userFilters,
Timeout: timeout, Timeout: timeout,
RootCA: rootCA,
LDAPAttributes: attributes, LDAPAttributes: attributes,
Options: options, Options: options,
} }
@ -198,6 +201,7 @@ type LDAPIDPChangedEvent struct {
UserObjectClasses []string `json:"userObjectClasses,omitempty"` UserObjectClasses []string `json:"userObjectClasses,omitempty"`
UserFilters []string `json:"userFilters,omitempty"` UserFilters []string `json:"userFilters,omitempty"`
Timeout *time.Duration `json:"timeout,omitempty"` Timeout *time.Duration `json:"timeout,omitempty"`
RootCA []byte `json:"rootCA,omitempty"`
LDAPAttributeChanges LDAPAttributeChanges
OptionChanges OptionChanges
@ -315,6 +319,12 @@ func ChangeLDAPTimeout(timeout time.Duration) func(*LDAPIDPChangedEvent) {
} }
} }
func ChangeLDAPRootCA(rootCA []byte) func(*LDAPIDPChangedEvent) {
return func(e *LDAPIDPChangedEvent) {
e.RootCA = rootCA
}
}
func ChangeLDAPAttributes(attributes LDAPAttributeChanges) func(*LDAPIDPChangedEvent) { func ChangeLDAPAttributes(attributes LDAPAttributeChanges) func(*LDAPIDPChangedEvent) {
return func(e *LDAPIDPChangedEvent) { return func(e *LDAPIDPChangedEvent) {
e.LDAPAttributeChanges = attributes e.LDAPAttributeChanges = attributes

View File

@ -852,6 +852,7 @@ func NewLDAPIDPAddedEvent(
userObjectClasses []string, userObjectClasses []string,
userFilters []string, userFilters []string,
timeout time.Duration, timeout time.Duration,
rootCA []byte,
attributes idp.LDAPAttributes, attributes idp.LDAPAttributes,
options idp.Options, options idp.Options,
) *LDAPIDPAddedEvent { ) *LDAPIDPAddedEvent {
@ -874,6 +875,7 @@ func NewLDAPIDPAddedEvent(
userObjectClasses, userObjectClasses,
userFilters, userFilters,
timeout, timeout,
rootCA,
attributes, attributes,
options, options,
), ),

View File

@ -852,6 +852,7 @@ func NewLDAPIDPAddedEvent(
userObjectClasses []string, userObjectClasses []string,
userFilters []string, userFilters []string,
timeout time.Duration, timeout time.Duration,
rootCA []byte,
attributes idp.LDAPAttributes, attributes idp.LDAPAttributes,
options idp.Options, options idp.Options,
) *LDAPIDPAddedEvent { ) *LDAPIDPAddedEvent {
@ -874,6 +875,7 @@ func NewLDAPIDPAddedEvent(
userObjectClasses, userObjectClasses,
userFilters, userFilters,
timeout, timeout,
rootCA,
attributes, attributes,
options, options,
), ),

View File

@ -6834,6 +6834,8 @@ message AddLDAPProviderRequest {
google.protobuf.Duration timeout = 10; google.protobuf.Duration timeout = 10;
zitadel.idp.v1.LDAPAttributes attributes = 11; zitadel.idp.v1.LDAPAttributes attributes = 11;
zitadel.idp.v1.Options provider_options = 12; zitadel.idp.v1.Options provider_options = 12;
// Root_ca is for self signing certificates for TLS connections to LDAP servers it is intended to be filled with a .pem file.
bytes root_ca = 13 [(validate.rules).bytes.max_len = 12000];
} }
message AddLDAPProviderResponse { message AddLDAPProviderResponse {
@ -6855,6 +6857,8 @@ message UpdateLDAPProviderRequest {
google.protobuf.Duration timeout = 11; google.protobuf.Duration timeout = 11;
zitadel.idp.v1.LDAPAttributes attributes = 12; zitadel.idp.v1.LDAPAttributes attributes = 12;
zitadel.idp.v1.Options provider_options = 13; zitadel.idp.v1.Options provider_options = 13;
// Root_ca is for self signing certificates for TLS connections to LDAP servers it is intended to be filled with a .pem file.
bytes root_ca = 14 [(validate.rules).bytes.max_len = 12000];
} }
message UpdateLDAPProviderResponse { message UpdateLDAPProviderResponse {

View File

@ -456,6 +456,7 @@ message LDAPConfig {
repeated string user_filters = 7; repeated string user_filters = 7;
google.protobuf.Duration timeout = 8; google.protobuf.Duration timeout = 8;
LDAPAttributes attributes = 9; LDAPAttributes attributes = 9;
bytes root_ca = 10;
} }
message SAMLConfig { message SAMLConfig {

View File

@ -10,24 +10,23 @@ import "protoc-gen-openapiv2/options/annotations.proto";
import "validate/validate.proto"; import "validate/validate.proto";
import "google/protobuf/duration.proto"; import "google/protobuf/duration.proto";
option go_package = "github.com/zitadel/zitadel/pkg/grpc/idp/v2;idp"; option go_package = "github.com/zitadel/zitadel/pkg/grpc/idp/v2;idp";
message IDP { message IDP {
// Unique identifier for the identity provider. // Unique identifier for the identity provider.
string id = 1 [ string id = 1
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"69629023906488334\""; example:
} "\"69629023906488334\"";
]; } ];
zitadel.object.v2.Details details = 2; zitadel.object.v2.Details details = 2;
// Current state of the identity provider. // Current state of the identity provider.
IDPState state = 3; IDPState state = 3;
string name = 4 [ string name = 4
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"Google\""; example:
} "\"Google\"";
]; } ];
// Type of the identity provider, for example OIDC, JWT, LDAP and SAML. // Type of the identity provider, for example OIDC, JWT, LDAP and SAML.
IDPType type = 5; IDPType type = 5;
// Configuration for the type of the identity provider. // Configuration for the type of the identity provider.
@ -94,176 +93,188 @@ message JWTConfig {
// The endpoint where the JWT can be extracted. // The endpoint where the JWT can be extracted.
string jwt_endpoint = 1 [ string jwt_endpoint = 1 [
(validate.rules).string = {min_len : 1, max_len : 200}, (validate.rules).string = {min_len : 1, max_len : 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) =
example: "\"https://accounts.google.com\""; {
example:
"\"https://accounts.google.com\"";
} }
]; ];
// The issuer of the JWT (for validation). // The issuer of the JWT (for validation).
string issuer = 2 [ string issuer = 2 [
(validate.rules).string = {min_len : 1, max_len : 200}, (validate.rules).string = {min_len : 1, max_len : 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) =
example: "\"https://accounts.google.com\""; {
example:
"\"https://accounts.google.com\"";
} }
]; ];
// The endpoint to the key (JWK) which is used to sign the JWT with. // The endpoint to the key (JWK) which is used to sign the JWT with.
string keys_endpoint = 3 [ string keys_endpoint = 3 [
(validate.rules).string = {min_len : 1, max_len : 200}, (validate.rules).string = {min_len : 1, max_len : 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) =
example: "\"https://accounts.google.com/keys\""; {
example:
"\"https://accounts.google.com/keys\"";
} }
]; ];
// The name of the header where the JWT is sent in, default is authorization. // The name of the header where the JWT is sent in, default is authorization.
string header_name = 4 [ string header_name = 4 [
(validate.rules).string = {min_len : 1, max_len : 200}, (validate.rules).string = {min_len : 1, max_len : 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) =
example: "\"x-auth-token\""; {
example:
"\"x-auth-token\"";
} }
]; ];
} }
message OAuthConfig { message OAuthConfig {
// Client id generated by the identity provider. // Client id generated by the identity provider.
string client_id = 1 [ string client_id = 1
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"client-id\""; example:
} "\"client-id\"";
]; } ];
// The endpoint where ZITADEL send the user to authenticate. // The endpoint where ZITADEL send the user to authenticate.
string authorization_endpoint = 2 [ string authorization_endpoint = 2
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"https://accounts.google.com/o/oauth2/v2/auth\""; example:
} "\"https://accounts.google.com/o/oauth2/v2/auth\"";
]; } ];
// The endpoint where ZITADEL can get the token. // The endpoint where ZITADEL can get the token.
string token_endpoint = 3 [ string token_endpoint = 3
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"https://oauth2.googleapis.com/token\""; example:
} "\"https://oauth2.googleapis.com/token\"";
]; } ];
// The endpoint where ZITADEL can get the user information. // The endpoint where ZITADEL can get the user information.
string user_endpoint = 4 [ string user_endpoint = 4
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"https://openidconnect.googleapis.com/v1/userinfo\""; example:
} "\"https://openidconnect.googleapis.com/v1/userinfo\"";
]; } ];
// The scopes requested by ZITADEL during the request on the identity provider. // The scopes requested by ZITADEL during the request on the identity
repeated string scopes = 5 [ // provider.
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { repeated string scopes = 5
example: "[\"openid\", \"profile\", \"email\"]"; [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
} example:
]; "[\"openid\", \"profile\", \"email\"]";
// Defines how the attribute is called where ZITADEL can get the id of the user. } ];
string id_attribute = 6 [ // Defines how the attribute is called where ZITADEL can get the id of the
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { // user.
example: "\"user_id\""; string id_attribute = 6
} [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
]; example:
"\"user_id\"";
} ];
} }
message GenericOIDCConfig { message GenericOIDCConfig {
// The OIDC issuer of the identity provider. // The OIDC issuer of the identity provider.
string issuer = 1 [ string issuer = 1
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"https://accounts.google.com/\""; example:
} "\"https://accounts.google.com/\"";
]; } ];
// Client id generated by the identity provider. // Client id generated by the identity provider.
string client_id = 2 [ string client_id = 2
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"client-id\""; example:
} "\"client-id\"";
]; } ];
// The scopes requested by ZITADEL during the request on the identity provider. // The scopes requested by ZITADEL during the request on the identity
repeated string scopes = 3 [ // provider.
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { repeated string scopes = 3
example: "[\"openid\", \"profile\", \"email\"]"; [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
} example:
]; "[\"openid\", \"profile\", \"email\"]";
// If true, provider information get mapped from the id token, not from the userinfo endpoint. } ];
bool is_id_token_mapping = 4 [ // If true, provider information get mapped from the id token, not from the
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { // userinfo endpoint.
example: "true"; bool is_id_token_mapping = 4
} [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
]; example:
"true";
} ];
} }
message GitHubConfig { message GitHubConfig {
// The client ID of the GitHub App. // The client ID of the GitHub App.
string client_id = 1 [ string client_id = 1
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"client-id\""; example:
} "\"client-id\"";
]; } ];
// The scopes requested by ZITADEL during the request to GitHub. // The scopes requested by ZITADEL during the request to GitHub.
repeated string scopes = 2 [ repeated string scopes = 2
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"openid\", \"profile\", \"email\"]"; example:
} "[\"openid\", \"profile\", \"email\"]";
]; } ];
} }
message GitHubEnterpriseServerConfig { message GitHubEnterpriseServerConfig {
// The client ID of the GitHub App. // The client ID of the GitHub App.
string client_id = 1 [ string client_id = 1
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"client-id\""; example:
} "\"client-id\"";
]; } ];
string authorization_endpoint = 2; string authorization_endpoint = 2;
string token_endpoint = 3; string token_endpoint = 3;
string user_endpoint = 4; string user_endpoint = 4;
// The scopes requested by ZITADEL during the request to GitHub. // The scopes requested by ZITADEL during the request to GitHub.
repeated string scopes = 5 [ repeated string scopes = 5
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"openid\", \"profile\", \"email\"]"; example:
} "[\"openid\", \"profile\", \"email\"]";
]; } ];
} }
message GoogleConfig { message GoogleConfig {
// Client id of the Google application. // Client id of the Google application.
string client_id = 1 [ string client_id = 1
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"client-id\""; example:
} "\"client-id\"";
]; } ];
// The scopes requested by ZITADEL during the request to Google. // The scopes requested by ZITADEL during the request to Google.
repeated string scopes = 2 [ repeated string scopes = 2
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"openid\", \"profile\", \"email\"]"; example:
} "[\"openid\", \"profile\", \"email\"]";
]; } ];
} }
message GitLabConfig { message GitLabConfig {
// Client id of the GitLab application. // Client id of the GitLab application.
string client_id = 1 [ string client_id = 1
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"client-id\""; example:
} "\"client-id\"";
]; } ];
// The scopes requested by ZITADEL during the request to GitLab. // The scopes requested by ZITADEL during the request to GitLab.
repeated string scopes = 2 [ repeated string scopes = 2
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"openid\", \"profile\", \"email\"]"; example:
} "[\"openid\", \"profile\", \"email\"]";
]; } ];
} }
message GitLabSelfHostedConfig { message GitLabSelfHostedConfig {
string issuer = 1; string issuer = 1;
// Client id of the GitLab application. // Client id of the GitLab application.
string client_id = 2 [ string client_id = 2
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"client-id\""; example:
} "\"client-id\"";
]; } ];
// The scopes requested by ZITADEL during the request to GitLab. // The scopes requested by ZITADEL during the request to GitLab.
repeated string scopes = 3 [ repeated string scopes = 3
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"openid\", \"profile\", \"email\"]"; example:
} "[\"openid\", \"profile\", \"email\"]";
]; } ];
} }
message LDAPConfig { message LDAPConfig {
@ -276,6 +287,7 @@ message LDAPConfig {
repeated string user_filters = 7; repeated string user_filters = 7;
google.protobuf.Duration timeout = 8; google.protobuf.Duration timeout = 8;
LDAPAttributes attributes = 9; LDAPAttributes attributes = 9;
bytes root_ca = 10;
} }
message SAMLConfig { message SAMLConfig {
@ -288,49 +300,60 @@ message SAMLConfig {
// `nameid-format` for the SAML Request. // `nameid-format` for the SAML Request.
SAMLNameIDFormat name_id_format = 4; SAMLNameIDFormat name_id_format = 4;
// Optional name of the attribute, which will be used to map the user // Optional name of the attribute, which will be used to map the user
// in case the nameid-format returned is `urn:oasis:names:tc:SAML:2.0:nameid-format:transient`. // in case the nameid-format returned is
// `urn:oasis:names:tc:SAML:2.0:nameid-format:transient`.
optional string transient_mapping_attribute_name = 5; optional string transient_mapping_attribute_name = 5;
} }
message AzureADConfig { message AzureADConfig {
// Client id of the Azure AD application // Client id of the Azure AD application
string client_id = 1 [ string client_id = 1
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"client-id\""; example:
} "\"client-id\"";
]; } ];
// Defines what user accounts should be able to login (Personal, Organizational, All). // Defines what user accounts should be able to login (Personal,
// Organizational, All).
AzureADTenant tenant = 2; AzureADTenant tenant = 2;
// Azure AD doesn't send if the email has been verified. Enable this if the user email should always be added verified in ZITADEL (no verification emails will be sent). // Azure AD doesn't send if the email has been verified. Enable this if the
// user email should always be added verified in ZITADEL (no verification
// emails will be sent).
bool email_verified = 3; bool email_verified = 3;
// The scopes requested by ZITADEL during the request to Azure AD. // The scopes requested by ZITADEL during the request to Azure AD.
repeated string scopes = 4 [ repeated string scopes = 4
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"openid\", \"profile\", \"email\", \"User.Read\"]"; example:
} "[\"openid\", \"profile\", \"email\", \"User.Read\"]";
]; } ];
} }
message Options { message Options {
// Enable if users should be able to link an existing ZITADEL user with an external account. // Enable if users should be able to link an existing ZITADEL user with an
// external account.
bool is_linking_allowed = 1; bool is_linking_allowed = 1;
// Enable if users should be able to create a new account in ZITADEL when using an external account. // Enable if users should be able to create a new account in ZITADEL when
// using an external account.
bool is_creation_allowed = 2; bool is_creation_allowed = 2;
// Enable if a new account in ZITADEL should be created automatically when login with an external account. // Enable if a new account in ZITADEL should be created automatically when
// login with an external account.
bool is_auto_creation = 3; bool is_auto_creation = 3;
// Enable if a the ZITADEL account fields should be updated automatically on each login. // Enable if a the ZITADEL account fields should be updated automatically on
// each login.
bool is_auto_update = 4; bool is_auto_update = 4;
// Enable if users should get prompted to link an existing ZITADEL user to an external account if the selected attribute matches. // Enable if users should get prompted to link an existing ZITADEL user to an
// external account if the selected attribute matches.
AutoLinkingOption auto_linking = 5; AutoLinkingOption auto_linking = 5;
} }
enum AutoLinkingOption { enum AutoLinkingOption {
// AUTO_LINKING_OPTION_UNSPECIFIED disables the auto linking prompt. // AUTO_LINKING_OPTION_UNSPECIFIED disables the auto linking prompt.
AUTO_LINKING_OPTION_UNSPECIFIED = 0; AUTO_LINKING_OPTION_UNSPECIFIED = 0;
// AUTO_LINKING_OPTION_USERNAME will use the username of the external user to check for a corresponding ZITADEL user. // AUTO_LINKING_OPTION_USERNAME will use the username of the external user to
// check for a corresponding ZITADEL user.
AUTO_LINKING_OPTION_USERNAME = 1; AUTO_LINKING_OPTION_USERNAME = 1;
// AUTO_LINKING_OPTION_EMAIL will use the email of the external user to check for a corresponding ZITADEL user with the same verified email // AUTO_LINKING_OPTION_EMAIL will use the email of the external user to check
// Note that in case multiple users match, no prompt will be shown. // for a corresponding ZITADEL user with the same verified email Note that in
// case multiple users match, no prompt will be shown.
AUTO_LINKING_OPTION_EMAIL = 2; AUTO_LINKING_OPTION_EMAIL = 2;
} }
@ -338,16 +361,23 @@ message LDAPAttributes {
string id_attribute = 1 [ (validate.rules).string = {max_len : 200} ]; string id_attribute = 1 [ (validate.rules).string = {max_len : 200} ];
string first_name_attribute = 2 [ (validate.rules).string = {max_len : 200} ]; string first_name_attribute = 2 [ (validate.rules).string = {max_len : 200} ];
string last_name_attribute = 3 [ (validate.rules).string = {max_len : 200} ]; string last_name_attribute = 3 [ (validate.rules).string = {max_len : 200} ];
string display_name_attribute = 4 [(validate.rules).string = {max_len: 200}]; string display_name_attribute = 4
[ (validate.rules).string = {max_len : 200} ];
string nick_name_attribute = 5 [ (validate.rules).string = {max_len : 200} ]; string nick_name_attribute = 5 [ (validate.rules).string = {max_len : 200} ];
string preferred_username_attribute = 6 [(validate.rules).string = {max_len: 200}]; string preferred_username_attribute = 6
[ (validate.rules).string = {max_len : 200} ];
string email_attribute = 7 [ (validate.rules).string = {max_len : 200} ]; string email_attribute = 7 [ (validate.rules).string = {max_len : 200} ];
string email_verified_attribute = 8 [(validate.rules).string = {max_len: 200}]; string email_verified_attribute = 8
[ (validate.rules).string = {max_len : 200} ];
string phone_attribute = 9 [ (validate.rules).string = {max_len : 200} ]; string phone_attribute = 9 [ (validate.rules).string = {max_len : 200} ];
string phone_verified_attribute = 10 [(validate.rules).string = {max_len: 200}]; string phone_verified_attribute = 10
string preferred_language_attribute = 11 [(validate.rules).string = {max_len: 200}]; [ (validate.rules).string = {max_len : 200} ];
string avatar_url_attribute = 12 [(validate.rules).string = {max_len: 200}]; string preferred_language_attribute = 11
[ (validate.rules).string = {max_len : 200} ];
string avatar_url_attribute = 12
[ (validate.rules).string = {max_len : 200} ];
string profile_attribute = 13 [ (validate.rules).string = {max_len : 200} ]; string profile_attribute = 13 [ (validate.rules).string = {max_len : 200} ];
string root_ca= 14;
} }
enum AzureADTenantType { enum AzureADTenantType {
@ -365,27 +395,27 @@ message AzureADTenant {
message AppleConfig { message AppleConfig {
// Client id (App ID or Service ID) provided by Apple. // Client id (App ID or Service ID) provided by Apple.
string client_id = 1 [ string client_id = 1
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"com.client.id\""; example:
} "\"com.client.id\"";
]; } ];
// Team ID provided by Apple. // Team ID provided by Apple.
string team_id = 2 [ string team_id = 2
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"ALT03JV3OS\""; example:
} "\"ALT03JV3OS\"";
]; } ];
// ID of the private key generated by Apple. // ID of the private key generated by Apple.
string key_id = 3 [ string key_id = 3
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"OGKDK25KD\""; example:
} "\"OGKDK25KD\"";
]; } ];
// The scopes requested by ZITADEL during the request to Apple. // The scopes requested by ZITADEL during the request to Apple.
repeated string scopes = 4 [ repeated string scopes = 4
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { [ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"name\", \"email\"]"; example:
} "[\"name\", \"email\"]";
]; } ];
} }

View File

@ -13255,6 +13255,8 @@ message AddLDAPProviderRequest {
google.protobuf.Duration timeout = 10; google.protobuf.Duration timeout = 10;
zitadel.idp.v1.LDAPAttributes attributes = 11; zitadel.idp.v1.LDAPAttributes attributes = 11;
zitadel.idp.v1.Options provider_options = 12; zitadel.idp.v1.Options provider_options = 12;
// Root_ca is for self signing certificates for TLS connections to LDAP servers it is intended to be filled with a .pem file.
bytes root_ca = 13 [(validate.rules).bytes.max_len = 12000];
} }
message AddLDAPProviderResponse { message AddLDAPProviderResponse {
@ -13276,6 +13278,8 @@ message UpdateLDAPProviderRequest {
google.protobuf.Duration timeout = 11; google.protobuf.Duration timeout = 11;
zitadel.idp.v1.LDAPAttributes attributes = 12; zitadel.idp.v1.LDAPAttributes attributes = 12;
zitadel.idp.v1.Options provider_options = 13; zitadel.idp.v1.Options provider_options = 13;
// Root_ca is for self signing certificates for TLS connections to LDAP servers it is intended to be filled with a .pem file.
bytes root_ca = 14 [(validate.rules).bytes.max_len = 12000];
} }
message UpdateLDAPProviderResponse { message UpdateLDAPProviderResponse {