mirror of
https://github.com/grafana/grafana.git
synced 2025-02-20 11:48:34 -06:00
feat(ldap): work on reading ldap config from toml file, #1450
This commit is contained in:
parent
262a09bb2d
commit
0b5ba55131
@ -9,7 +9,7 @@ watch_dirs = [
|
||||
"$WORKDIR/public/views",
|
||||
"$WORKDIR/conf",
|
||||
]
|
||||
watch_exts = [".go", ".ini"]
|
||||
watch_exts = [".go", "conf/*"]
|
||||
build_delay = 1500
|
||||
cmds = [
|
||||
["go", "build", "-o", "./bin/grafana-server"],
|
||||
|
7
build.go
7
build.go
@ -128,6 +128,7 @@ type linuxPackageOptions struct {
|
||||
binPath string
|
||||
configDir string
|
||||
configFilePath string
|
||||
ldapFilePath string
|
||||
etcDefaultPath string
|
||||
etcDefaultFilePath string
|
||||
initdScriptFilePath string
|
||||
@ -148,6 +149,7 @@ func createLinuxPackages() {
|
||||
binPath: "/usr/sbin/grafana-server",
|
||||
configDir: "/etc/grafana",
|
||||
configFilePath: "/etc/grafana/grafana.ini",
|
||||
ldapFilePath: "/etc/grafana/ldap.toml",
|
||||
etcDefaultPath: "/etc/default",
|
||||
etcDefaultFilePath: "/etc/default/grafana-server",
|
||||
initdScriptFilePath: "/etc/init.d/grafana-server",
|
||||
@ -167,6 +169,7 @@ func createLinuxPackages() {
|
||||
binPath: "/usr/sbin/grafana-server",
|
||||
configDir: "/etc/grafana",
|
||||
configFilePath: "/etc/grafana/grafana.ini",
|
||||
ldapFilePath: "/etc/grafana/ldap.toml",
|
||||
etcDefaultPath: "/etc/sysconfig",
|
||||
etcDefaultFilePath: "/etc/sysconfig/grafana-server",
|
||||
initdScriptFilePath: "/etc/init.d/grafana-server",
|
||||
@ -204,8 +207,10 @@ func createPackage(options linuxPackageOptions) {
|
||||
runPrint("cp", "-a", filepath.Join(workingDir, "tmp")+"/.", filepath.Join(packageRoot, options.homeDir))
|
||||
// remove bin path
|
||||
runPrint("rm", "-rf", filepath.Join(packageRoot, options.homeDir, "bin"))
|
||||
// copy sample ini file to /etc/opt/grafana
|
||||
// copy sample ini file to /etc/grafana
|
||||
runPrint("cp", "conf/sample.ini", filepath.Join(packageRoot, options.configFilePath))
|
||||
// copy sample ldap toml config file to /etc/grafana/ldap.toml
|
||||
runPrint("cp", "conf/sample.ini", filepath.Join(packageRoot, ldapFilePath))
|
||||
|
||||
args := []string{
|
||||
"-s", "dir",
|
||||
|
@ -181,22 +181,8 @@ auto_sign_up = true
|
||||
|
||||
#################################### Auth LDAP ##########################
|
||||
[auth.ldap]
|
||||
enabled = true
|
||||
hosts = ldap://127.0.0.1:389
|
||||
use_ssl = false
|
||||
bind_path = cn=%s,dc=grafana,dc=org
|
||||
bind_password =
|
||||
search_bases = dc=grafana,dc=org
|
||||
search_filter = (cn=%s)
|
||||
attr_username = cn
|
||||
attr_name = givenName
|
||||
attr_surname = sn
|
||||
attr_email = email
|
||||
attr_member_of = memberOf
|
||||
|
||||
[auth.ldap.member.to.role.map]
|
||||
-: cn=admins,dc=grafana,dc=org -> "Admin" in "Main Org."
|
||||
-: cn=users,dc=grafana,dc=org -> "Viewer" in "Main Org."
|
||||
enabled = false
|
||||
config_file = /etc/grafana/ldap.toml
|
||||
|
||||
#################################### SMTP / Emailing ##########################
|
||||
[smtp]
|
||||
|
31
conf/ldap.toml
Normal file
31
conf/ldap.toml
Normal file
@ -0,0 +1,31 @@
|
||||
verbose_logging = true
|
||||
|
||||
[[servers]]
|
||||
host = "127.0.0.1"
|
||||
port = 389
|
||||
use_ssl = false
|
||||
|
||||
bind_dn = "cn=admin,dc=grafana,dc=org"
|
||||
bind_password = "grafana"
|
||||
|
||||
search_filter = "(cn=%s)"
|
||||
search_base_dns = ["dc=grafana,dc=org"]
|
||||
|
||||
[servers.attributes]
|
||||
name = "givenName"
|
||||
surname = "sn"
|
||||
username = "cn"
|
||||
member_of = "memberOf"
|
||||
email = "email"
|
||||
|
||||
[[servers.group_mappings]]
|
||||
group_dn = "cn=admins,dc=grafana,dc=org"
|
||||
org_role = "Admin"
|
||||
|
||||
[[server.ldap_group_to_org_role_mappings]]
|
||||
group_dn = "cn=users,dc=grafana,dc=org"
|
||||
org_role = "Editor"
|
||||
|
||||
[[servers.group_mappings]]
|
||||
group_dn = "*"
|
||||
org_role = "Viewer"
|
@ -178,6 +178,11 @@
|
||||
[auth.basic]
|
||||
;enabled = true
|
||||
|
||||
#################################### Auth LDAP ##########################
|
||||
[auth.ldap]
|
||||
enabled = false
|
||||
config_file = /etc/grafana/ldap.toml
|
||||
|
||||
#################################### SMTP / Emailing ##########################
|
||||
[smtp]
|
||||
;enabled = false
|
||||
|
2
main.go
2
main.go
@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/cmd"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/login"
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/eventpublisher"
|
||||
@ -54,6 +55,7 @@ func main() {
|
||||
initRuntime()
|
||||
|
||||
search.Init()
|
||||
login.Init()
|
||||
social.NewOAuthService()
|
||||
eventpublisher.Init()
|
||||
plugins.Init()
|
||||
|
@ -4,9 +4,9 @@ import (
|
||||
"net/url"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/auth"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/login"
|
||||
"github.com/grafana/grafana/pkg/metrics"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
@ -88,13 +88,13 @@ func LoginApiPing(c *middleware.Context) {
|
||||
}
|
||||
|
||||
func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) Response {
|
||||
authQuery := auth.AuthenticateUserQuery{
|
||||
authQuery := login.LoginUserQuery{
|
||||
Username: cmd.User,
|
||||
Password: cmd.Password,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(&authQuery); err != nil {
|
||||
if err == auth.ErrInvalidCredentials {
|
||||
if err == login.ErrInvalidCredentials {
|
||||
return ApiError(401, "Invalid username or password", err)
|
||||
}
|
||||
|
||||
|
@ -1,27 +0,0 @@
|
||||
package auth
|
||||
|
||||
import m "github.com/grafana/grafana/pkg/models"
|
||||
|
||||
type LdapGroupToOrgRole struct {
|
||||
GroupDN string
|
||||
OrgId int64
|
||||
OrgRole m.RoleType
|
||||
}
|
||||
|
||||
type LdapServerConf struct {
|
||||
Host string
|
||||
Port string
|
||||
UseSSL bool
|
||||
BindDN string
|
||||
BindPassword string
|
||||
AttrUsername string
|
||||
AttrName string
|
||||
AttrSurname string
|
||||
AttrEmail string
|
||||
AttrMemberOf string
|
||||
|
||||
SearchFilter string
|
||||
SearchBaseDNs []string
|
||||
|
||||
LdapGroups []*LdapGroupToOrgRole
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package auth
|
||||
package login
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@ -13,24 +13,25 @@ var (
|
||||
ErrInvalidCredentials = errors.New("Invalid Username or Password")
|
||||
)
|
||||
|
||||
type AuthenticateUserQuery struct {
|
||||
type LoginUserQuery struct {
|
||||
Username string
|
||||
Password string
|
||||
User *m.User
|
||||
}
|
||||
|
||||
func init() {
|
||||
func Init() {
|
||||
bus.AddHandler("auth", AuthenticateUser)
|
||||
loadLdapConfig()
|
||||
}
|
||||
|
||||
func AuthenticateUser(query *AuthenticateUserQuery) error {
|
||||
func AuthenticateUser(query *LoginUserQuery) error {
|
||||
err := loginUsingGrafanaDB(query)
|
||||
if err == nil || err != ErrInvalidCredentials {
|
||||
return err
|
||||
}
|
||||
|
||||
if setting.LdapEnabled {
|
||||
for _, server := range ldapServers {
|
||||
for _, server := range ldapCfg.Servers {
|
||||
auther := NewLdapAuthenticator(server)
|
||||
err = auther.login(query)
|
||||
if err == nil || err != ErrInvalidCredentials {
|
||||
@ -42,7 +43,7 @@ func AuthenticateUser(query *AuthenticateUserQuery) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func loginUsingGrafanaDB(query *AuthenticateUserQuery) error {
|
||||
func loginUsingGrafanaDB(query *LoginUserQuery) error {
|
||||
userQuery := m.GetUserByLoginQuery{LoginOrEmail: query.Username}
|
||||
|
||||
if err := bus.Dispatch(&userQuery); err != nil {
|
@ -1,40 +1,17 @@
|
||||
package auth
|
||||
package login
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/go-ldap/ldap"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
var ldapServers []*LdapServerConf
|
||||
|
||||
func init() {
|
||||
ldapServers = []*LdapServerConf{
|
||||
{
|
||||
UseSSL: false,
|
||||
Host: "127.0.0.1",
|
||||
Port: "389",
|
||||
BindDN: "cn=admin,dc=grafana,dc=org",
|
||||
BindPassword: "grafana",
|
||||
AttrName: "givenName",
|
||||
AttrSurname: "sn",
|
||||
AttrUsername: "cn",
|
||||
AttrMemberOf: "memberOf",
|
||||
AttrEmail: "email",
|
||||
SearchFilter: "(cn=%s)",
|
||||
SearchBaseDNs: []string{"dc=grafana,dc=org"},
|
||||
LdapGroups: []*LdapGroupToOrgRole{
|
||||
{GroupDN: "cn=users,dc=grafana,dc=org", OrgId: 1, OrgRole: m.ROLE_VIEWER},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type ldapAuther struct {
|
||||
server *LdapServerConf
|
||||
conn *ldap.Conn
|
||||
@ -45,7 +22,7 @@ func NewLdapAuthenticator(server *LdapServerConf) *ldapAuther {
|
||||
}
|
||||
|
||||
func (a *ldapAuther) Dial() error {
|
||||
address := fmt.Sprintf("%s:%s", a.server.Host, a.server.Port)
|
||||
address := fmt.Sprintf("%s:%d", a.server.Host, a.server.Port)
|
||||
var err error
|
||||
if a.server.UseSSL {
|
||||
a.conn, err = ldap.DialTLS("tcp", address, nil)
|
||||
@ -56,7 +33,7 @@ func (a *ldapAuther) Dial() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *ldapAuther) login(query *AuthenticateUserQuery) error {
|
||||
func (a *ldapAuther) login(query *LoginUserQuery) error {
|
||||
if err := a.Dial(); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -71,10 +48,9 @@ func (a *ldapAuther) login(query *AuthenticateUserQuery) error {
|
||||
if ldapUser, err := a.searchForUser(query.Username); err != nil {
|
||||
return err
|
||||
} else {
|
||||
log.Info("Surname: %s", ldapUser.LastName)
|
||||
log.Info("givenName: %s", ldapUser.FirstName)
|
||||
log.Info("email: %s", ldapUser.Email)
|
||||
log.Info("memberOf: %s", ldapUser.MemberOf)
|
||||
if ldapCfg.VerboseLogging {
|
||||
log.Info("Ldap User Info: %s", spew.Sdump(ldapUser))
|
||||
}
|
||||
|
||||
// check if a second user bind is needed
|
||||
if a.server.BindPassword != "" {
|
||||
@ -164,6 +140,8 @@ func (a *ldapAuther) syncOrgRoles(user *m.User, ldapUser *ldapUserInfo) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// ignore subsequent ldap group mapping matches
|
||||
break
|
||||
} else {
|
||||
// remove role
|
||||
cmd := m.RemoveOrgUserCommand{OrgId: org.OrgId, UserId: user.Id}
|
||||
@ -244,11 +222,11 @@ func (a *ldapAuther) searchForUser(username string) (*ldapUserInfo, error) {
|
||||
Scope: ldap.ScopeWholeSubtree,
|
||||
DerefAliases: ldap.NeverDerefAliases,
|
||||
Attributes: []string{
|
||||
a.server.AttrUsername,
|
||||
a.server.AttrSurname,
|
||||
a.server.AttrEmail,
|
||||
a.server.AttrName,
|
||||
a.server.AttrMemberOf,
|
||||
a.server.Attr.Username,
|
||||
a.server.Attr.Surname,
|
||||
a.server.Attr.Email,
|
||||
a.server.Attr.Name,
|
||||
a.server.Attr.MemberOf,
|
||||
},
|
||||
Filter: fmt.Sprintf(a.server.SearchFilter, username),
|
||||
}
|
||||
@ -264,20 +242,20 @@ func (a *ldapAuther) searchForUser(username string) (*ldapUserInfo, error) {
|
||||
}
|
||||
|
||||
if len(searchResult.Entries) == 0 {
|
||||
return nil, errors.New("Ldap search matched no entry, please review your filter setting.")
|
||||
return nil, ErrInvalidCredentials
|
||||
}
|
||||
|
||||
if len(searchResult.Entries) > 1 {
|
||||
return nil, errors.New("Ldap search matched mopre than one entry, please review your filter setting")
|
||||
return nil, errors.New("Ldap search matched more than one entry, please review your filter setting")
|
||||
}
|
||||
|
||||
return &ldapUserInfo{
|
||||
DN: searchResult.Entries[0].DN,
|
||||
LastName: getLdapAttr(a.server.AttrSurname, searchResult),
|
||||
FirstName: getLdapAttr(a.server.AttrName, searchResult),
|
||||
Username: getLdapAttr(a.server.AttrUsername, searchResult),
|
||||
Email: getLdapAttr(a.server.AttrEmail, searchResult),
|
||||
MemberOf: getLdapAttrArray(a.server.AttrMemberOf, searchResult),
|
||||
LastName: getLdapAttr(a.server.Attr.Surname, searchResult),
|
||||
FirstName: getLdapAttr(a.server.Attr.Name, searchResult),
|
||||
Username: getLdapAttr(a.server.Attr.Username, searchResult),
|
||||
Email: getLdapAttr(a.server.Attr.Email, searchResult),
|
||||
MemberOf: getLdapAttrArray(a.server.Attr.MemberOf, searchResult),
|
||||
}, nil
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package auth
|
||||
package login
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@ -139,6 +139,25 @@ func TestLdapAuther(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
ldapAutherScenario("given multiple matching ldap groups", func(sc *scenarioContext) {
|
||||
ldapAuther := NewLdapAuthenticator(&LdapServerConf{
|
||||
LdapGroups: []*LdapGroupToOrgRole{
|
||||
{GroupDN: "cn=admins", OrgId: 1, OrgRole: "Admin"},
|
||||
{GroupDN: "*", OrgId: 1, OrgRole: "Viewer"},
|
||||
},
|
||||
})
|
||||
|
||||
sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_ADMIN}})
|
||||
err := ldapAuther.syncOrgRoles(&m.User{}, &ldapUserInfo{
|
||||
MemberOf: []string{"cn=admins"},
|
||||
})
|
||||
|
||||
Convey("Should take first match, and ignore subsequent matches", func() {
|
||||
So(err, ShouldBeNil)
|
||||
So(sc.updateOrgUserCmd, ShouldBeNil)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package auth
|
||||
package login
|
||||
|
||||
type ldapUserInfo struct {
|
||||
DN string
|
65
pkg/login/settings.go
Normal file
65
pkg/login/settings.go
Normal file
@ -0,0 +1,65 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type LdapConfig struct {
|
||||
Servers []*LdapServerConf `toml:"servers"`
|
||||
VerboseLogging bool `toml:"verbose_logging"`
|
||||
}
|
||||
|
||||
type LdapServerConf struct {
|
||||
Host string `toml:"host"`
|
||||
Port int `toml:"port"`
|
||||
UseSSL bool `toml:"use_ssl"`
|
||||
BindDN string `toml:"bind_dn"`
|
||||
BindPassword string `toml:"bind_password"`
|
||||
Attr LdapAttributeMap `toml:"attributes"`
|
||||
|
||||
SearchFilter string `toml:"search_filter"`
|
||||
SearchBaseDNs []string `toml:"search_base_dns"`
|
||||
|
||||
LdapGroups []*LdapGroupToOrgRole `toml:"group_mappings"`
|
||||
}
|
||||
|
||||
type LdapAttributeMap struct {
|
||||
Username string `toml:"username"`
|
||||
Name string `toml:"name"`
|
||||
Surname string `toml:"surname"`
|
||||
Email string `toml:"email"`
|
||||
MemberOf string `toml:"member_of"`
|
||||
}
|
||||
|
||||
type LdapGroupToOrgRole struct {
|
||||
GroupDN string `toml:"group_dn"`
|
||||
OrgId int64 `toml:"org_id"`
|
||||
OrgRole m.RoleType `toml:"org_role"`
|
||||
}
|
||||
|
||||
var ldapCfg LdapConfig
|
||||
|
||||
func loadLdapConfig() {
|
||||
if !setting.LdapEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
log.Info("Login: Ldap enabled, reading config file: %s", setting.LdapConfigFile)
|
||||
|
||||
_, err := toml.DecodeFile(setting.LdapConfigFile, &ldapCfg)
|
||||
if err != nil {
|
||||
log.Fatal(3, "Failed to load ldap config file: %s", err)
|
||||
}
|
||||
|
||||
// set default org id
|
||||
for _, server := range ldapCfg.Servers {
|
||||
for _, groupMap := range server.LdapGroups {
|
||||
if groupMap.OrgId == 0 {
|
||||
groupMap.OrgId = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -118,7 +118,8 @@ var (
|
||||
GoogleAnalyticsId string
|
||||
|
||||
// LDAP
|
||||
LdapEnabled bool
|
||||
LdapEnabled bool
|
||||
LdapConfigFile string
|
||||
|
||||
// SMTP email settings
|
||||
Smtp SmtpSettings
|
||||
@ -417,6 +418,7 @@ func NewConfigContext(args *CommandLineArgs) {
|
||||
|
||||
ldapSec := Cfg.Section("auth.ldap")
|
||||
LdapEnabled = ldapSec.Key("enabled").MustBool(false)
|
||||
LdapConfigFile = ldapSec.Key("config_file").String()
|
||||
|
||||
readSessionConfig()
|
||||
readSmtpSettings()
|
||||
|
Loading…
Reference in New Issue
Block a user