ldap

package
v1.0.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 29, 2020 License: Apache-2.0 Imports: 13 Imported by: 1

README

LDAP Backend

Getting Started

It is recommended reading the documentation for Local backend, because it outlines important principles of operation of all backends.

Additionally, the LDAP backend works in conjunction with Local backend. As you will see later, the two can be used together by introducing a dropdown in UI interface to choose local versus LDAP domain authentication.

The reference configuration for the backend is assets/conf/ldap/config.json.

The following Caddy endpoint at /auth authentications users from contoso.com domain.

There is a single LDAP server associated with the domain: ldaps://ldaps.contoso.com.

The plugin DOES NOT ignore certificate errors when connecting to the servers. However, one may ignore the errors by setting ignore_cert_errors to true.

The LDAP attribute mapping to JWT fields is as follows.

JWT Token Field LDAP Attribute
name givenName
surname sn
username sAMAccountName
member_of memberOf
email mail

The plugin uses authzsvc domain user to perform LDAP bind.

The base search DN is DC=CONTOSO,DC=COM.

The plugin accepts username (sAMAccountName) or email address (mail) and uses the following search filter: (&(|(sAMAccountName=%s)(mail=%s))(objectclass=user)).

For example:

      {
        "Name": "sAMAccountName",
        "Values": [
          "jsmith"
        ]
      },
      {
        "Name": "mail",
        "Values": [
          "jsmith@contoso.com"
        ]
      }

Upon successful authentication, the plugin assign the following rules to a user, provided the user is a member of a group:

JWT Role LDAP Group Membership
admin CN=Admins,OU=Security,OU=Groups,DC=CONTOSO,DC=COM
editor CN=Editors,OU=Security,OU=Groups,DC=CONTOSO,DC=COM
viewer CN=Viewers,OU=Security,OU=Groups,DC=CONTOSO,DC=COM

The security of the password could be improved by the following techniques:

  • pass the password via environment variable LDAP_USER_SECRET
  • store the password in a file and pass the file inside the password field with file: prefix, e.g. file:/path/to/password.

Model Configuration

{
  "match": [
    {
      "path": [
        "/auth*"
      ]
    }
  ],
  "handle": [
    {
      "handler": "authentication",
      "providers": {
        "portal": {
          "master": true,
          "auth_url_path": "/auth",
          "backends": [
            {
              "type": "local",
              "path": "assets/backends/local/users.json",
              "realm": "local"
            },
            {
              "type": "ldap",
              "realm": "contoso.com",
              "servers": [
                {
                  "addr": "ldaps://ldaps.contoso.com",
                  "ignore_cert_errors": true
                }
              ],
              "attributes": {
                "name": "givenName",
                "surname": "sn",
                "username": "sAMAccountName",
                "member_of": "memberOf",
                "email": "mail"
              },
              "username": "CN=authzsvc,OU=Service Accounts,OU=Administrative Accounts,DC=CONTOSO,DC=COM",
              "password": "P@ssW0rd123",
              "search_base_dn": "DC=CONTOSO,DC=COM",
              "search_filter": "(&(|(sAMAccountName=%s)(mail=%s))(objectclass=user))",
              "groups": [
                {
                  "dn": "CN=Admins,OU=Security,OU=Groups,DC=CONTOSO,DC=COM",
                  "roles": [
                    "admin"
                  ]
                },
                {
                  "dn": "CN=Editors,OU=Security,OU=Groups,DC=CONTOSO,DC=COM",
                  "roles": [
                    "editor"
                  ]
                },
                {
                  "dn": "CN=Viewers,OU=Security,OU=Groups,DC=CONTOSO,DC=COM",
                  "roles": [
                    "viewer"
                  ]
                }
              ]
            }
          ],
          "jwt": {
            "token_secret": "383aca9a-1c39-4d7a-b4d8-67ba4718dd3f",
            "token_issuer": "7a50e023-2c6e-4a5e-913e-23ecd0e2b940"
          },
          "ui": {
            "templates": {
              "login": "assets/ui/ldap/login.template",
              "portal": "assets/ui/portal.template"
            },
            "logo_url": "https://caddyserver.com/resources/images/caddy-circle-lock.svg",
            "logo_description": "Caddy",
            "allow_role_selection": false,
            "auto_redirect_url": "",
            "private_links": [
              {
                "title": "Prometheus",
                "link": "/prometheus"
              },
              {
                "title": "Alertmanager",
                "link": "/alertmanager"
              }
            ]
          }
        }
      }
    }
  ],
  "terminal": true
}

User Interface

Please notice that the login template uses different template from the plain Local backend.

          "ui": {
            "templates": {
              "login": "assets/ui/ldap/login.template",

The reason for that is the introduction of a dropbox or an input allowing a user to choose whether to use LDAP or Local backend when authenticating.

For example, the following code adds an HTML input:

The code is:

                <div class="input-field">
                  <input id="realm" name="realm" type="text" class="validate">
                  <label for="realm">Domain</label>
                </div>

It results in having free form input box.

A user may input the word local for Local backend and the name of the domain for LDAP backend.

The same could be accomplished with an HTML dropdown:

Add the following to the form:

                <div class="input-field">
                  <label>Domain</label>
                  <br /><br />
                  <select id="realm" name="realm" class="browser-default">
                    <option value="local" selected>Local</option>
                    <option value="contoso.com">CONTOSO.COM</option>
                  </select>
                </div>

Additionally, add the following to style:

      select {
        font-family: 'Roboto', sans-serif;
        color: #155D56;
      }

It results in having fixed dropdown box.

LDAP Authentication Process

The plugin does not keep connections open to LDAP servers. The plugin tears a connection down each time it finishes authenticating a request associated with the connection.

First, the plugin uses username and password to bind to an LDAP server. The purpose of the connection is searching for user objects in the server's directory.

The plugin takes the username provided in a request. Next, the plugin substitutes %s with the username in its search filter, i.e. (&(|(sAMAccountName=%s)(mail=%s))(objectclass=user)).

The plugin initiates a search for a user object in the scope provided via search_base_dn, e.g. DC=CONTOSO,DC=COM.

If the number of objects in the result of the search is not 1, then authentication fails.

Typically, the response would have the following structure:

[
  {
    "DN": "CN=Smith\\, John,OU=Users,DC=CONTOSO,DC=COM",
    "Attributes": [
      {
        "Name": "sn",
        "Values": [
          "Smith"
        ]
      },
      {
        "Name": "givenName",
        "Values": [
          "John"
        ]
      },
      {
        "Name": "memberOf",
        "Values": [
          "CN=Admins,OU=Security,OU=Groups,DC=CONTOSO,DC=COM",
          "CN=Editors,OU=Security,OU=Groups,DC=CONTOSO,DC=COM",
          "CN=Viewers,OU=Security,OU=Groups,DC=CONTOSO,DC=COM"
        ]
      },
      {
        "Name": "sAMAccountName",
        "Values": [
          "jsmith"
        ]
      },
      {
        "Name": "mail",
        "Values": [
          "jsmith@contoso.com"
        ]
      }
    ]
  }
]

The plugin iterates over memberOf attribute and compares the values to its group mapping:

              "groups": [
                {
                  "dn": "CN=Admins,OU=Security,OU=Groups,DC=CONTOSO,DC=COM",
                  "roles": [
                    "admin"
                  ]
                },
                {
                  "dn": "CN=Editors,OU=Security,OU=Groups,DC=CONTOSO,DC=COM",
                  "roles": [
                    "editor"
                  ]
                },
                {
                  "dn": "CN=Viewers,OU=Security,OU=Groups,DC=CONTOSO,DC=COM",
                  "roles": [
                    "viewer"
                  ]
                }
              ]

If there are no matches, the authentication fails.

Once the plugin determines the user's roles, e.g. admin, editor, viewer, the plugin actually checks whether the user's password is valid.

It does so by doing LDAP re-binding with the user's DN and the password provided in the request. In this example, the user's DN is CN=Smith\\, John,OU=Users,DC=CONTOSO,DC=COM.

If the re-binding is successful, the plugin issues a JWT token.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AuthServer

type AuthServer struct {
	Address          string   `json:"addr,omitempty"`
	URL              *url.URL `json:"-"`
	Port             string   `json:"-"`
	IgnoreCertErrors bool     `json:"ignore_cert_errors,omitempty"`
	Timeout          int      `json:"timeout,omitempty"`
}

AuthServer represents an instance of LDAP server.

type Authenticator

type Authenticator struct {
	// contains filtered or unexported fields
}

Authenticator represents database connector.

func NewAuthenticator

func NewAuthenticator() *Authenticator

NewAuthenticator returns an instance of Authenticator.

func (*Authenticator) AuthenticateUser

func (sa *Authenticator) AuthenticateUser(userInput, passwordInput string) (*jwt.UserClaims, int, error)

AuthenticateUser checks the database for the presence of a username/email and password and returns user claims.

func (*Authenticator) ConfigureBindCredentials

func (sa *Authenticator) ConfigureBindCredentials(username, password string) error

ConfigureBindCredentials configures user credentials for LDAP binding.

func (*Authenticator) ConfigureRealm

func (sa *Authenticator) ConfigureRealm(realm string) error

ConfigureRealm configures a domain name (realm) associated with the instance of authenticator.

func (*Authenticator) ConfigureSearch

func (sa *Authenticator) ConfigureSearch(attr UserAttributes, searchBaseDN string, searchFilter string) error

ConfigureSearch configures base DN, search filter, attributes for LDAP queries.

func (*Authenticator) ConfigureServers

func (sa *Authenticator) ConfigureServers(servers []AuthServer) error

ConfigureServers configures the addresses of LDAP servers.

func (*Authenticator) ConfigureUserGroups

func (sa *Authenticator) ConfigureUserGroups(groups []UserGroup) error

ConfigureUserGroups configures user group bindings for LDAP searching.

type Backend

type Backend struct {
	Realm         string                   `json:"realm,omitempty"`
	Servers       []AuthServer             `json:"servers,omitempty"`
	BindUsername  string                   `json:"username,omitempty"`
	BindPassword  string                   `json:"password,omitempty"`
	Attributes    UserAttributes           `json:"attributes,omitempty"`
	SearchBaseDN  string                   `json:"search_base_dn,omitempty"`
	SearchFilter  string                   `json:"search_filter,omitempty"`
	Groups        []UserGroup              `json:"groups,omitempty"`
	TokenProvider *jwt.TokenProviderConfig `json:"jwt,omitempty"`
	Authenticator *Authenticator           `json:"-"`
	// contains filtered or unexported fields
}

Backend represents authentication provider with SQLite backend.

func NewDatabaseBackend

func NewDatabaseBackend() *Backend

NewDatabaseBackend return an instance of authentication provider with SQLite backend.

func (*Backend) Authenticate

func (b *Backend) Authenticate(reqID string, kv map[string]string) (*jwt.UserClaims, int, error)

Authenticate performs authentication.

func (*Backend) ConfigureAuthenticator

func (b *Backend) ConfigureAuthenticator() error

ConfigureAuthenticator configures backend for .

func (*Backend) ConfigureLogger

func (b *Backend) ConfigureLogger(logger *zap.Logger) error

ConfigureLogger configures backend with the same logger as its user.

func (*Backend) ConfigureTokenProvider

func (b *Backend) ConfigureTokenProvider(upstream *jwt.TokenProviderConfig) error

ConfigureTokenProvider configures TokenProvider.

func (*Backend) GetRealm

func (b *Backend) GetRealm() string

GetRealm return authentication realm.

func (*Backend) Validate

func (b *Backend) Validate() error

Validate checks whether Backend is functional.

func (*Backend) ValidateConfig

func (b *Backend) ValidateConfig() error

ValidateConfig checks whether Backend has mandatory configuration.

type UserAttributes

type UserAttributes struct {
	Name     string `json:"name,omitempty"`
	Surname  string `json:"surname,omitempty"`
	Username string `json:"username,omitempty"`
	MemberOf string `json:"member_of,omitempty"`
	Email    string `json:"email,omitempty"`
}

UserAttributes represent the mapping of LDAP attributes to JWT fields.

type UserGroup

type UserGroup struct {
	GroupDN string   `json:"dn,omitempty"`
	Roles   []string `json:"roles,omitempty"`
}

UserGroup represent the binding between BaseDN and a serarch filter. Upon successful authentation for the combination, a user gets assigned the roles associated with the binding.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL