Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions conf/sample.ini
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,11 @@
;viewer_roles =
;verify_ssl_cert = true
;root_ca_pem_file = /etc/grafana/Keystone_CA.crt
# Whether to store keystone password in a cookie (true) or in a session variable (false)
;cookie_credentials = true
# Encryption key for storing keystone password (empty = no encryption)
# AES key should be 32 bytes
;credential_aes_key = 123456789,123456789,123456789,12

#################################### SMTP / Emailing ##########################
[smtp]
Expand Down
3 changes: 3 additions & 0 deletions pkg/api/dataproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/api/cloudwatch"
"github.com/grafana/grafana/pkg/api/keystone"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
Expand Down Expand Up @@ -67,6 +68,8 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string, targetUrl *url.URL) *ht
// clear cookie headers
req.Header.Del("Cookie")
req.Header.Del("Set-Cookie")

log.Info("Proxying call to %s", req.URL.String())
}

return &httputil.ReverseProxy{Director: director, FlushInterval: time.Millisecond * 200}
Expand Down
75 changes: 73 additions & 2 deletions pkg/api/keystone/keystone.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,17 @@ package keystone
import (
"time"

"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"errors"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"io"
)

const (
Expand All @@ -19,7 +25,13 @@ const (

func getUserName(c *middleware.Context) (string, error) {
var keystoneUserIdObj interface{}
if keystoneUserIdObj = c.Session.Get(middleware.SESS_KEY_USERID); keystoneUserIdObj == nil {
if setting.KeystoneCookieCredentials {
if keystoneUserIdObj = c.GetCookie(setting.CookieUserName); keystoneUserIdObj == nil {
return "", errors.New("Couldn't find cookie containing keystone userId")
} else {
return keystoneUserIdObj.(string), nil
}
} else if keystoneUserIdObj = c.Session.Get(middleware.SESS_KEY_USERID); keystoneUserIdObj == nil {
return "", errors.New("Session timed out trying to get keystone userId")
}

Expand Down Expand Up @@ -53,8 +65,23 @@ func getNewToken(c *middleware.Context) (string, error) {
}

var keystonePasswordObj interface{}
if keystonePasswordObj = c.Session.Get(middleware.SESS_KEY_PASSWORD); keystonePasswordObj == nil {
if setting.KeystoneCookieCredentials {
if keystonePasswordObj = c.GetCookie(middleware.SESS_KEY_PASSWORD); keystonePasswordObj == nil {
return "", errors.New("Couldn't find cookie containing keystone password")
} else {
log.Debug("Got password from cookie")
}
} else if keystonePasswordObj = c.Session.Get(middleware.SESS_KEY_PASSWORD); keystonePasswordObj == nil {
return "", errors.New("Session timed out trying to get keystone password")
} else if keystonePasswordObj != nil {
log.Debug("Got password from session")
}

if setting.KeystoneCredentialAesKey != "" {
keystonePasswordObj = decryptPassword(keystonePasswordObj.(string))
log.Debug("Decrypted password")
} else {
log.Warn("Password stored in cleartext!")
}

auth := Auth_data{
Expand All @@ -65,6 +92,11 @@ func getNewToken(c *middleware.Context) (string, error) {
Server: setting.KeystoneURL,
}
if err := AuthenticateScoped(&auth); err != nil {
if setting.KeystoneCookieCredentials {
c.SetCookie(middleware.SESS_KEY_PASSWORD, "", -1, setting.AppSubUrl+"/", nil, middleware.IsSecure(c), true)
} else {
c.Session.Set(middleware.SESS_KEY_PASSWORD, nil)
}
return "", err
}

Expand Down Expand Up @@ -124,3 +156,42 @@ func GetToken(c *middleware.Context) (string, error) {
}
return token, nil
}

func EncryptPassword(password string) string {
key := []byte(setting.KeystoneCredentialAesKey)
block, err := aes.NewCipher(key)
if err != nil {
log.Error(3, "Error: NewCipher(%d bytes) = %s", len(setting.KeystoneCredentialAesKey), err)
}
ciphertext := make([]byte, aes.BlockSize+len(password))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
log.Error(3, "Error: %s", err)
}
stream := cipher.NewOFB(block, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], []byte(password))

return base64.StdEncoding.EncodeToString(ciphertext)
}

func decryptPassword(base64ciphertext string) string {
key := []byte(setting.KeystoneCredentialAesKey)
block, err := aes.NewCipher(key)
if err != nil {
log.Error(3, "Error: NewCipher(%d bytes) = %s", len(setting.KeystoneCredentialAesKey), err)
}
ciphertext, err := base64.StdEncoding.DecodeString(base64ciphertext)
if err != nil {
log.Error(3, "Error: %s", err)
return ""
}
iv := ciphertext[:aes.BlockSize]
if aes.BlockSize > len(ciphertext) {
log.Error(3, "Error: ciphertext %s is shorter than AES blocksize %d", ciphertext, aes.BlockSize)
return ""
}
password := make([]byte, len(ciphertext)-aes.BlockSize)
stream := cipher.NewOFB(block, iv)
stream.XORKeyStream(password, ciphertext[aes.BlockSize:])
return string(password)
}
27 changes: 22 additions & 5 deletions pkg/api/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"net/url"

"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/keystone"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/login"
Expand Down Expand Up @@ -112,7 +113,21 @@ func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) Response {
loginUserWithUser(user, c)

if setting.KeystoneEnabled {
c.Session.Set(middleware.SESS_KEY_PASSWORD, cmd.Password)
if setting.KeystoneCredentialAesKey != "" {
cmd.Password = keystone.EncryptPassword(cmd.Password)
}
if setting.KeystoneCookieCredentials {
log.Debug("c.Req.Header.Get(\"X-Forwarded-Proto\"): %s", c.Req.Header.Get("X-Forwarded-Proto"))
var days interface{}
if setting.LogInRememberDays == 0 {
days = nil
} else {
days = 86400 * setting.LogInRememberDays
}
c.SetCookie(middleware.SESS_KEY_PASSWORD, cmd.Password, days, setting.AppSubUrl+"/", nil, middleware.IsSecure(c), true)
} else {
c.Session.Set(middleware.SESS_KEY_PASSWORD, cmd.Password)
}
}

result := map[string]interface{}{
Expand All @@ -136,16 +151,18 @@ func loginUserWithUser(user *m.User, c *middleware.Context) {

days := 86400 * setting.LogInRememberDays
if days > 0 {
c.SetCookie(setting.CookieUserName, user.Login, days, setting.AppSubUrl+"/")
c.SetSuperSecureCookie(util.EncodeMd5(user.Rands+user.Password), setting.CookieRememberName, user.Login, days, setting.AppSubUrl+"/")
c.SetCookie(setting.CookieUserName, user.Login, days, setting.AppSubUrl+"/", nil, middleware.IsSecure(c), true)
c.SetSuperSecureCookie(util.EncodeMd5(user.Rands+user.Password),
setting.CookieRememberName, user.Login, days, setting.AppSubUrl+"/", nil, middleware.IsSecure(c), true)
}

c.Session.Set(middleware.SESS_KEY_USERID, user.Id)
}

func Logout(c *middleware.Context) {
c.SetCookie(setting.CookieUserName, "", -1, setting.AppSubUrl+"/")
c.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubUrl+"/")
c.SetCookie(setting.CookieUserName, "", -1, setting.AppSubUrl+"/", nil, middleware.IsSecure(c), true)
c.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubUrl+"/", nil, middleware.IsSecure(c), true)
c.SetCookie(middleware.SESS_KEY_PASSWORD, "", -1, setting.AppSubUrl+"/", nil, middleware.IsSecure(c), true)
c.Session.Destory(c)
c.Redirect(setting.AppSubUrl + "/login")
}
4 changes: 4 additions & 0 deletions pkg/middleware/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,7 @@ func (ctx *Context) HasUserRole(role m.RoleType) bool {
func (ctx *Context) TimeRequest(timer metrics.Timer) {
ctx.Data["perfmon.timer"] = timer
}

func IsSecure(ctx *Context) bool {
return (ctx.Req.TLS != nil) || (ctx.Req.Header.Get("X-Forwarded-Proto") == "https")
}
5 changes: 2 additions & 3 deletions pkg/middleware/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ import (
)

const (
SESS_KEY_USERID = "uid"
SESS_KEY_USERID = "uid"
SESS_KEY_OAUTH_STATE = "state"
SESS_KEY_APIKEY = "apikey_id" // used fror render requests with api keys
SESS_KEY_PASSWORD = "password"
SESS_KEY_PASSWORD = "grafana_password"
)

var sessionManager *session.Manager
Expand Down
26 changes: 15 additions & 11 deletions pkg/setting/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,17 +139,19 @@ var (
LdapAllowSignup bool = true

// Keystone
KeystoneEnabled bool
KeystoneURL string
KeystoneDefaultDomain string
KeystoneDefaultRole string
KeystoneViewerRoles []string
KeystoneReadEditorRoles []string
KeystoneEditorRoles []string
KeystoneAdminRoles []string
KeystoneGlobalAdminRoles []string
KeystoneVerifySSLCert bool
KeystoneRootCAPEMFile string
KeystoneEnabled bool
KeystoneCookieCredentials bool
KeystoneCredentialAesKey string
KeystoneURL string
KeystoneDefaultDomain string
KeystoneDefaultRole string
KeystoneViewerRoles []string
KeystoneReadEditorRoles []string
KeystoneEditorRoles []string
KeystoneAdminRoles []string
KeystoneGlobalAdminRoles []string
KeystoneVerifySSLCert bool
KeystoneRootCAPEMFile string

// SMTP email settings
Smtp SmtpSettings
Expand Down Expand Up @@ -572,6 +574,8 @@ func NewConfigContext(args *CommandLineArgs) error {

keystone := Cfg.Section("auth.keystone")
KeystoneEnabled = keystone.Key("enabled").MustBool(false)
KeystoneCookieCredentials = keystone.Key("cookie_credentials").MustBool(false)
KeystoneCredentialAesKey = keystone.Key("credential_aes_key").String()
KeystoneURL = keystone.Key("auth_url").String()
KeystoneDefaultDomain = keystone.Key("default_domain").String()
KeystoneDefaultRole = keystone.Key("default_role").String()
Expand Down