Skip to content

Commit

Permalink
Split out search DNs for users, groups and roles to separate configs.
Browse files Browse the repository at this point in the history
Also update to more modern baton patterns like a configuration pkg.
  • Loading branch information
pquerna committed Oct 15, 2024
1 parent c7db6ef commit 858d700
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 184 deletions.
70 changes: 0 additions & 70 deletions cmd/baton-ldap/config.go

This file was deleted.

17 changes: 5 additions & 12 deletions cmd/baton-ldap/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"

"github.com/conductorone/baton-ldap/pkg/config"
"github.com/conductorone/baton-ldap/pkg/connector"
configschema "github.com/conductorone/baton-sdk/pkg/config"
"github.com/conductorone/baton-sdk/pkg/connectorbuilder"
Expand All @@ -19,7 +20,7 @@ var version = "dev"
func main() {
ctx := context.Background()

_, cmd, err := configschema.DefineConfiguration(ctx, "baton-ldap", getConnector, configuration)
_, cmd, err := configschema.DefineConfiguration(ctx, "baton-ldap", getConnector, config.Configuration)
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
Expand All @@ -37,22 +38,14 @@ func main() {
func getConnector(ctx context.Context, v *viper.Viper) (types.ConnectorServer, error) {
l := ctxzap.Extract(ctx)

if err := validateConfig(ctx, v); err != nil {
cf, err := config.New(ctx, v)
if err != nil {
return nil, err
}

if v.GetString(urlField.FieldName) == "" && v.GetString(domainField.FieldName) != "" {
v.Set(urlField.FieldName, fmt.Sprintf("ldap://%s", v.GetString(domainField.FieldName)))
}

ldapConnector, err := connector.New(
ctx,
v.GetString(urlField.FieldName),
v.GetString(baseDNField.FieldName),
v.GetString(passwordField.FieldName),
v.GetString(userDNField.FieldName),
v.GetBool(disableOperationalAttrsField.FieldName),
v.GetBool(insecureSkipVerifyField.FieldName),
cf,
)
if err != nil {
l.Error("error creating connector", zap.Error(err))
Expand Down
171 changes: 171 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package config

import (
"context"
"fmt"
"net/url"

"github.com/conductorone/baton-sdk/pkg/field"
"github.com/go-ldap/ldap/v3"
"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
"github.com/spf13/viper"
)

var (
urlField = field.StringField("url", field.WithDescription(`The URL to connect to. Example: "ldaps://baton.example.com"`))

baseDNField = field.StringField("base-dn", field.WithDescription(`The base DN to search from. Example: "DC=baton,DC=example,DC=com"`))
passwordField = field.StringField("password", field.WithDescription("The password to bind to the LDAP server"))
bindDNField = field.StringField("bind-dn", field.WithDescription("The user DN to bind to the LDAP server"))

userSearchDNField = field.StringField("user-search-dn",
field.WithDescription("The DN to search for users under. Example: 'OU=Users,DC=baton,DC=example,DC=com'"))
groupSearchDNField = field.StringField("group-search-dn",
field.WithDescription("The DN to search for groups under. Example: 'OU=Groups,DC=baton,DC=example,DC=com'"))
roleSearchDNField = field.StringField("role-search-dn",
field.WithDescription("The DN to search for roles under. Example: 'OU=Roles,DC=baton,DC=example,DC=com'"))

//revive:disable-next-line:line-length-limit
disableOperationalAttrsField = field.BoolField("disable-operational-attrs", field.WithDescription("Disable fetching operational attributes. Some LDAP servers don't support these. If disabled, created_at and last login info will not be fetched"))
insecureSkipVerifyField = field.BoolField("insecure-skip-verify", field.WithDescription("If connecting over TLS, skip verifying the server certificate"))
)

var (
// depreciated: use urlField
domainField = field.StringField("domain", field.WithDescription(`The fully-qualified LDAP domain to connect to. Example: "baton.example.com (depreciated, use url"`), field.WithHidden(true))

// depreciated: use userBindDNField
userDNField = field.StringField("user-dn", field.WithDescription("The user DN to bind to the LDAP server (depreciated, use user-bind-dn)"), field.WithHidden(true))
)

// configurationFields defines the external configuration required for the connector to run.
var ConfigurationFields = []field.SchemaField{
urlField,
domainField,
baseDNField,
passwordField,
userDNField,
bindDNField,
userSearchDNField,
groupSearchDNField,
roleSearchDNField,
insecureSkipVerifyField,
disableOperationalAttrsField,
}

var ConfigRelations = []field.SchemaFieldRelationship{
field.FieldsMutuallyExclusive(domainField, urlField),
field.FieldsAtLeastOneUsed(domainField, urlField),
}

var Configuration = field.NewConfiguration(ConfigurationFields, ConfigRelations...)

func New(ctx context.Context, v *viper.Viper) (*Config, error) {
l := ctxzap.Extract(ctx)

rv := &Config{}
if urlstr := v.GetString(urlField.FieldName); urlstr != "" {
ux, err := url.Parse(urlstr)
if err != nil {
return nil, fmt.Errorf("error parsing url: %w", err)
}
switch ux.Scheme {
case "ldap", "ldaps":
rv.ServerURL = ux
default:
return nil, fmt.Errorf("unsupported scheme: %s", ux.Scheme)
}
} else if domainValue := v.GetString(domainField.FieldName); domainValue != "" {
rv.ServerURL = &url.URL{
Scheme: "ldap",
Host: domainValue,
}
}

if rv.ServerURL == nil {
return nil, fmt.Errorf("missing server URL")
}

if baseDNValue := v.GetString(baseDNField.FieldName); baseDNValue != "" {
baseDN, err := ldap.ParseDN(baseDNValue)
if err != nil {
return nil, fmt.Errorf("error parsing base-dn: %w", err)
}
rv.BaseDN = baseDN
}

if userDNValue := v.GetString(userDNField.FieldName); userDNValue != "" {
userDN, err := ldap.ParseDN(userDNValue)
if err != nil {
return nil, fmt.Errorf("error parsing user-dn: %w", err)
}
rv.BindDN = userDN
}

if bindDNValue := v.GetString(bindDNField.FieldName); bindDNValue != "" {
bindDN, err := ldap.ParseDN(bindDNValue)
if err != nil {
return nil, fmt.Errorf("error parsing bind-dn: %w", err)
}
rv.BindDN = bindDN
}

if rv.BindDN == nil {
return nil, fmt.Errorf("missing bind-dn")
}

if userSearchDNValue := v.GetString(userSearchDNField.FieldName); userSearchDNValue != "" {
userSearchDN, err := ldap.ParseDN(userSearchDNValue)
if err != nil {
return nil, fmt.Errorf("error parsing user-search-dn: %w", err)
}
rv.UserSearchDN = userSearchDN
} else {
rv.UserSearchDN = rv.BaseDN
}

if groupSearchDNValue := v.GetString(groupSearchDNField.FieldName); groupSearchDNValue != "" {
groupSearchDN, err := ldap.ParseDN(groupSearchDNValue)
if err != nil {
return nil, fmt.Errorf("error parsing group-search-dn: %w", err)
}
rv.GroupSearchDN = groupSearchDN
} else {
rv.GroupSearchDN = rv.BaseDN
}

if roleSearchDNValue := v.GetString(roleSearchDNField.FieldName); roleSearchDNValue != "" {
roleSearchDN, err := ldap.ParseDN(roleSearchDNValue)
if err != nil {
return nil, fmt.Errorf("error parsing role-search-dn: %w", err)
}
rv.RoleSearchDN = roleSearchDN
} else {
rv.RoleSearchDN = rv.BaseDN
}

rv.BindPassword = v.GetString(passwordField.FieldName)
if rv.BindPassword == "" {
l.Warn("No password supplied. Will try an unauthenticated bind")
}

rv.InsecureSkipVerify = v.GetBool(insecureSkipVerifyField.FieldName)
rv.DisableOperationalAttrs = v.GetBool(disableOperationalAttrsField.FieldName)

return rv, nil
}

type Config struct {
ServerURL *url.URL
BaseDN *ldap.DN

BindPassword string
BindDN *ldap.DN

UserSearchDN *ldap.DN
GroupSearchDN *ldap.DN
RoleSearchDN *ldap.DN

DisableOperationalAttrs bool
InsecureSkipVerify bool
}
40 changes: 22 additions & 18 deletions pkg/connector/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import (
"context"
"fmt"

"github.com/conductorone/baton-ldap/pkg/config"
"github.com/conductorone/baton-ldap/pkg/ldap"
v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
"github.com/conductorone/baton-sdk/pkg/annotations"
"github.com/conductorone/baton-sdk/pkg/connectorbuilder"
ldap3 "github.com/go-ldap/ldap/v3"
"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
"go.uber.org/zap"
)
Expand Down Expand Up @@ -38,15 +40,15 @@ var (
)

type LDAP struct {
client *ldap.Client
disableOperationalAttrs bool
client *ldap.Client
config *config.Config
}

func (l *LDAP) ResourceSyncers(ctx context.Context) []connectorbuilder.ResourceSyncer {
return []connectorbuilder.ResourceSyncer{
userBuilder(l.client, l.disableOperationalAttrs),
groupBuilder(l.client),
roleBuilder(l.client),
userBuilder(l.client, l.config.UserSearchDN, l.config.DisableOperationalAttrs),
groupBuilder(l.client, l.config.GroupSearchDN, l.config.UserSearchDN),
roleBuilder(l.client, l.config.RoleSearchDN),
}
}

Expand All @@ -62,36 +64,38 @@ func (l *LDAP) Metadata(ctx context.Context) (*v2.ConnectorMetadata, error) {
func (l *LDAP) Validate(ctx context.Context) (annotations.Annotations, error) {
_, _, err := l.client.LdapSearch(
ctx,
ldap3.ScopeBaseObject,
nil,
"(objectClass=*)",
nil,
"",
1,
"",
)
if err != nil {
return nil, fmt.Errorf("ldap-connector: failed to validate user credentials: %w", err)
return nil, fmt.Errorf("ldap-connector: failed to validate connection: %w", err)
}
return nil, nil
}

// New returns the LDAP connector.
func New(ctx context.Context, serverUrl string, baseDN string, password string, userDN string, disableOperationalAttrs bool, insecureSkipVerify bool) (*LDAP, error) {
func New(ctx context.Context, cf *config.Config) (*LDAP, error) {
l := ctxzap.Extract(ctx)
l.Debug("creating new LDAP connector",
zap.Stringer("server_url", cf.ServerURL),
zap.Stringer("bind_dn", cf.BindDN),
zap.Bool("disable_operational_attrs", cf.DisableOperationalAttrs))

l.Debug("creating new LDAP connector", zap.String("serverUrl", serverUrl), zap.String("baseDN", baseDN), zap.Bool("disableOperationalAttrs", disableOperationalAttrs))
conn, err := ldap.TestConnection(serverUrl, insecureSkipVerify)
if err != nil {
return nil, err
}
defer conn.Close()

ldapClient, err := ldap.NewClient(ctx, serverUrl, baseDN, password, userDN, insecureSkipVerify)
ldapClient, err := ldap.NewClient(ctx,
cf.ServerURL.String(),
cf.BindPassword,
cf.BindDN.String(),
cf.InsecureSkipVerify)
if err != nil {
return nil, err
}

return &LDAP{
client: ldapClient,
disableOperationalAttrs: disableOperationalAttrs,
client: ldapClient,
config: cf,
}, nil
}
Loading

0 comments on commit 858d700

Please sign in to comment.