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 Nov 21, 2016
1 parent 32a011e commit a487ee3
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 17 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
72 changes: 70 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,28 @@ 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 setting.KeystoneCredentialAesKey != "" {
c.GetCookie(middleware.SESS_KEY_PASSWORD)
} else {
keystonePasswordObj = c.GetCookie(middleware.SESS_KEY_PASSWORD)
}
if 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 +97,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, "", 0)
} else {
c.Session.Set(middleware.SESS_KEY_PASSWORD, nil)
}
return "", err
}

Expand Down Expand Up @@ -124,3 +161,34 @@ 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)
iv := ciphertext[:aes.BlockSize]
password := make([]byte, len(ciphertext)-aes.BlockSize)
stream := cipher.NewOFB(block, iv)
stream.XORKeyStream(password, ciphertext[aes.BlockSize:])
return string(password)
}
10 changes: 9 additions & 1 deletion 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,14 @@ 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 {
c.SetCookie(middleware.SESS_KEY_PASSWORD, cmd.Password)
} else {
c.Session.Set(middleware.SESS_KEY_PASSWORD, cmd.Password)
}
}

result := map[string]interface{}{
Expand Down
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 = "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

0 comments on commit a487ee3

Please sign in to comment.