Skip to content

Commit 38a0156

Browse files
committed
Fixes #5
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
1 parent 32a011e commit 38a0156

File tree

6 files changed

+121
-21
lines changed

6 files changed

+121
-21
lines changed

conf/sample.ini

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,11 @@
268268
;viewer_roles =
269269
;verify_ssl_cert = true
270270
;root_ca_pem_file = /etc/grafana/Keystone_CA.crt
271+
# Whether to store keystone password in a cookie (true) or in a session variable (false)
272+
;cookie_credentials = true
273+
# Encryption key for storing keystone password (empty = no encryption)
274+
# AES key should be 32 bytes
275+
;credential_aes_key = 123456789,123456789,123456789,12
271276

272277
#################################### SMTP / Emailing ##########################
273278
[smtp]

pkg/api/keystone/keystone.go

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@ package keystone
33
import (
44
"time"
55

6+
"crypto/aes"
7+
"crypto/cipher"
8+
"crypto/rand"
9+
"encoding/base64"
610
"errors"
711
"github.com/grafana/grafana/pkg/bus"
12+
"github.com/grafana/grafana/pkg/log"
813
"github.com/grafana/grafana/pkg/middleware"
914
m "github.com/grafana/grafana/pkg/models"
1015
"github.com/grafana/grafana/pkg/setting"
16+
"io"
1117
)
1218

1319
const (
@@ -19,7 +25,13 @@ const (
1925

2026
func getUserName(c *middleware.Context) (string, error) {
2127
var keystoneUserIdObj interface{}
22-
if keystoneUserIdObj = c.Session.Get(middleware.SESS_KEY_USERID); keystoneUserIdObj == nil {
28+
if setting.KeystoneCookieCredentials {
29+
if keystoneUserIdObj = c.GetCookie(setting.CookieUserName); keystoneUserIdObj == nil {
30+
return "", errors.New("Couldn't find cookie containing keystone userId")
31+
} else {
32+
return keystoneUserIdObj.(string), nil
33+
}
34+
} else if keystoneUserIdObj = c.Session.Get(middleware.SESS_KEY_USERID); keystoneUserIdObj == nil {
2335
return "", errors.New("Session timed out trying to get keystone userId")
2436
}
2537

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

5567
var keystonePasswordObj interface{}
56-
if keystonePasswordObj = c.Session.Get(middleware.SESS_KEY_PASSWORD); keystonePasswordObj == nil {
68+
if setting.KeystoneCookieCredentials {
69+
if keystonePasswordObj = c.GetCookie(middleware.SESS_KEY_PASSWORD); keystonePasswordObj == nil {
70+
return "", errors.New("Couldn't find cookie containing keystone password")
71+
} else {
72+
log.Debug("Got password from cookie")
73+
}
74+
} else if keystonePasswordObj = c.Session.Get(middleware.SESS_KEY_PASSWORD); keystonePasswordObj == nil {
5775
return "", errors.New("Session timed out trying to get keystone password")
76+
} else if keystonePasswordObj != nil {
77+
log.Debug("Got password from session")
78+
}
79+
80+
if setting.KeystoneCredentialAesKey != "" {
81+
keystonePasswordObj = decryptPassword(keystonePasswordObj.(string))
82+
log.Debug("Decrypted password")
83+
} else {
84+
log.Warn("Password stored in cleartext!")
5885
}
5986

6087
auth := Auth_data{
@@ -65,6 +92,11 @@ func getNewToken(c *middleware.Context) (string, error) {
6592
Server: setting.KeystoneURL,
6693
}
6794
if err := AuthenticateScoped(&auth); err != nil {
95+
if setting.KeystoneCookieCredentials {
96+
c.SetCookie(middleware.SESS_KEY_PASSWORD, "", -1, setting.AppSubUrl+"/", nil, middleware.IsSecure(c), true)
97+
} else {
98+
c.Session.Set(middleware.SESS_KEY_PASSWORD, nil)
99+
}
68100
return "", err
69101
}
70102

@@ -124,3 +156,42 @@ func GetToken(c *middleware.Context) (string, error) {
124156
}
125157
return token, nil
126158
}
159+
160+
func EncryptPassword(password string) string {
161+
key := []byte(setting.KeystoneCredentialAesKey)
162+
block, err := aes.NewCipher(key)
163+
if err != nil {
164+
log.Error(3, "Error: NewCipher(%d bytes) = %s", len(setting.KeystoneCredentialAesKey), err)
165+
}
166+
ciphertext := make([]byte, aes.BlockSize+len(password))
167+
iv := ciphertext[:aes.BlockSize]
168+
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
169+
log.Error(3, "Error: %s", err)
170+
}
171+
stream := cipher.NewOFB(block, iv)
172+
stream.XORKeyStream(ciphertext[aes.BlockSize:], []byte(password))
173+
174+
return base64.StdEncoding.EncodeToString(ciphertext)
175+
}
176+
177+
func decryptPassword(base64ciphertext string) string {
178+
key := []byte(setting.KeystoneCredentialAesKey)
179+
block, err := aes.NewCipher(key)
180+
if err != nil {
181+
log.Error(3, "Error: NewCipher(%d bytes) = %s", len(setting.KeystoneCredentialAesKey), err)
182+
}
183+
ciphertext, err := base64.StdEncoding.DecodeString(base64ciphertext)
184+
if err != nil {
185+
log.Error(3, "Error: %s", err)
186+
return ""
187+
}
188+
iv := ciphertext[:aes.BlockSize]
189+
if aes.BlockSize > len(ciphertext) {
190+
log.Error(3, "Error: ciphertext %s is shorter than AES blocksize %d", ciphertext, aes.BlockSize)
191+
return ""
192+
}
193+
password := make([]byte, len(ciphertext)-aes.BlockSize)
194+
stream := cipher.NewOFB(block, iv)
195+
stream.XORKeyStream(password, ciphertext[aes.BlockSize:])
196+
return string(password)
197+
}

pkg/api/login.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"net/url"
55

66
"github.com/grafana/grafana/pkg/api/dtos"
7+
"github.com/grafana/grafana/pkg/api/keystone"
78
"github.com/grafana/grafana/pkg/bus"
89
"github.com/grafana/grafana/pkg/log"
910
"github.com/grafana/grafana/pkg/login"
@@ -112,7 +113,21 @@ func LoginPost(c *middleware.Context, cmd dtos.LoginCommand) Response {
112113
loginUserWithUser(user, c)
113114

114115
if setting.KeystoneEnabled {
115-
c.Session.Set(middleware.SESS_KEY_PASSWORD, cmd.Password)
116+
if setting.KeystoneCredentialAesKey != "" {
117+
cmd.Password = keystone.EncryptPassword(cmd.Password)
118+
}
119+
if setting.KeystoneCookieCredentials {
120+
log.Debug("c.Req.Header.Get(\"X-Forwarded-Proto\"): %s", c.Req.Header.Get("X-Forwarded-Proto"))
121+
var days interface{}
122+
if setting.LogInRememberDays == 0 {
123+
days = nil
124+
} else {
125+
days = 86400 * setting.LogInRememberDays
126+
}
127+
c.SetCookie(middleware.SESS_KEY_PASSWORD, cmd.Password, days, setting.AppSubUrl+"/", nil, middleware.IsSecure(c), true)
128+
} else {
129+
c.Session.Set(middleware.SESS_KEY_PASSWORD, cmd.Password)
130+
}
116131
}
117132

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

137152
days := 86400 * setting.LogInRememberDays
138153
if days > 0 {
139-
c.SetCookie(setting.CookieUserName, user.Login, days, setting.AppSubUrl+"/")
140-
c.SetSuperSecureCookie(util.EncodeMd5(user.Rands+user.Password), setting.CookieRememberName, user.Login, days, setting.AppSubUrl+"/")
154+
c.SetCookie(setting.CookieUserName, user.Login, days, setting.AppSubUrl+"/", nil, middleware.IsSecure(c), true)
155+
c.SetSuperSecureCookie(util.EncodeMd5(user.Rands+user.Password),
156+
setting.CookieRememberName, user.Login, days, setting.AppSubUrl+"/", nil, middleware.IsSecure(c), true)
141157
}
142158

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

146162
func Logout(c *middleware.Context) {
147-
c.SetCookie(setting.CookieUserName, "", -1, setting.AppSubUrl+"/")
148-
c.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubUrl+"/")
163+
c.SetCookie(setting.CookieUserName, "", -1, setting.AppSubUrl+"/", nil, middleware.IsSecure(c), true)
164+
c.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubUrl+"/", nil, middleware.IsSecure(c), true)
165+
c.SetCookie(middleware.SESS_KEY_PASSWORD, "", -1, setting.AppSubUrl+"/", nil, middleware.IsSecure(c), true)
149166
c.Session.Destory(c)
150167
c.Redirect(setting.AppSubUrl + "/login")
151168
}

pkg/middleware/middleware.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,7 @@ func (ctx *Context) HasUserRole(role m.RoleType) bool {
232232
func (ctx *Context) TimeRequest(timer metrics.Timer) {
233233
ctx.Data["perfmon.timer"] = timer
234234
}
235+
236+
func IsSecure(ctx *Context) bool {
237+
return (ctx.Req.TLS != nil) || (ctx.Req.Header.Get("X-Forwarded-Proto") == "https")
238+
}

pkg/middleware/session.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@ import (
1212
)
1313

1414
const (
15-
SESS_KEY_USERID = "uid"
15+
SESS_KEY_USERID = "uid"
1616
SESS_KEY_OAUTH_STATE = "state"
17-
SESS_KEY_APIKEY = "apikey_id" // used fror render requests with api keys
18-
SESS_KEY_PASSWORD = "password"
17+
SESS_KEY_PASSWORD = "grafana_password"
1918
)
2019

2120
var sessionManager *session.Manager

pkg/setting/setting.go

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -139,17 +139,19 @@ var (
139139
LdapAllowSignup bool = true
140140

141141
// Keystone
142-
KeystoneEnabled bool
143-
KeystoneURL string
144-
KeystoneDefaultDomain string
145-
KeystoneDefaultRole string
146-
KeystoneViewerRoles []string
147-
KeystoneReadEditorRoles []string
148-
KeystoneEditorRoles []string
149-
KeystoneAdminRoles []string
150-
KeystoneGlobalAdminRoles []string
151-
KeystoneVerifySSLCert bool
152-
KeystoneRootCAPEMFile string
142+
KeystoneEnabled bool
143+
KeystoneCookieCredentials bool
144+
KeystoneCredentialAesKey string
145+
KeystoneURL string
146+
KeystoneDefaultDomain string
147+
KeystoneDefaultRole string
148+
KeystoneViewerRoles []string
149+
KeystoneReadEditorRoles []string
150+
KeystoneEditorRoles []string
151+
KeystoneAdminRoles []string
152+
KeystoneGlobalAdminRoles []string
153+
KeystoneVerifySSLCert bool
154+
KeystoneRootCAPEMFile string
153155

154156
// SMTP email settings
155157
Smtp SmtpSettings
@@ -572,6 +574,8 @@ func NewConfigContext(args *CommandLineArgs) error {
572574

573575
keystone := Cfg.Section("auth.keystone")
574576
KeystoneEnabled = keystone.Key("enabled").MustBool(false)
577+
KeystoneCookieCredentials = keystone.Key("cookie_credentials").MustBool(false)
578+
KeystoneCredentialAesKey = keystone.Key("credential_aes_key").String()
575579
KeystoneURL = keystone.Key("auth_url").String()
576580
KeystoneDefaultDomain = keystone.Key("default_domain").String()
577581
KeystoneDefaultRole = keystone.Key("default_role").String()

0 commit comments

Comments
 (0)