Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Configuration options:
cookie_credentials - set to true to use cookie instead of session for storing Keystone password
credential_aes_key - 32-char encryption key. If set, this key is used to encrypt/decrypt the stored Keystone password
  • Loading branch information
dhague committed Feb 2, 2017
1 parent 83d006a commit c652f2f
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 21 deletions.
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
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 @@ -140,17 +140,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 @@ -574,6 +576,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

0 comments on commit c652f2f

Please sign in to comment.