Skip to content

Commit 10d7241

Browse files
authored
Merge pull request #4 from sapcc/v2.6.0-keystone
Keystone authentication improvements and logging
2 parents fed74d1 + 6bb8613 commit 10d7241

File tree

9 files changed

+222
-28
lines changed

9 files changed

+222
-28
lines changed

conf/defaults.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,11 +202,14 @@ config_file = /etc/grafana/ldap.toml
202202
enabled = false
203203
auth_url = http://localhost:5000
204204
default_domain = default
205+
#default_role = Viewer
205206
global_admin_roles =
206207
admin_roles = admin
207208
editor_roles = _member_
208209
read_editor_roles =
209210
viewer_roles =
211+
verify_ssl_cert = true
212+
root_ca_pem_file =
210213

211214
#################################### SMTP / Emailing ##########################
212215
[smtp]

conf/sample.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,14 @@
195195
;auth_url = http://localhost:5000
196196
;v3 = false
197197
;default_domain = default
198+
;default_role = Viewer
198199
;global_admin_roles =
199200
;admin_roles = admin
200201
;editor_roles = _member_
201202
;read_editor_roles =
202203
;viewer_roles =
204+
;verify_ssl_cert = true
205+
;root_ca_pem_file = /etc/grafana/Keystone_CA.crt
203206

204207
#################################### SMTP / Emailing ##########################
205208
[smtp]

pkg/api/keystone/keystone.go

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package keystone
33
import (
44
"time"
55

6+
"errors"
67
"github.com/grafana/grafana/pkg/bus"
78
"github.com/grafana/grafana/pkg/middleware"
89
m "github.com/grafana/grafana/pkg/models"
@@ -17,7 +18,12 @@ const (
1718
)
1819

1920
func getUserName(c *middleware.Context) (string, error) {
20-
userQuery := m.GetUserByIdQuery{Id: c.Session.Get(middleware.SESS_KEY_USERID).(int64)}
21+
var keystoneUserIdObj interface{}
22+
if keystoneUserIdObj = c.Session.Get(middleware.SESS_KEY_USERID); keystoneUserIdObj == nil {
23+
return "", errors.New("Session timed out trying to get keystone userId")
24+
}
25+
26+
userQuery := m.GetUserByIdQuery{Id: keystoneUserIdObj.(int64)}
2127
if err := bus.Dispatch(&userQuery); err != nil {
2228
if err == m.ErrUserNotFound {
2329
return "", err
@@ -46,10 +52,15 @@ func getNewToken(c *middleware.Context) (string, error) {
4652
return "", err
4753
}
4854

55+
var keystonePasswordObj interface{}
56+
if keystonePasswordObj = c.Session.Get(middleware.SESS_KEY_PASSWORD); keystonePasswordObj == nil {
57+
return "", errors.New("Session timed out trying to get keystone password")
58+
}
59+
4960
auth := Auth_data{
5061
Username: username,
5162
Project: project,
52-
Password: c.Session.Get(middleware.SESS_KEY_PASSWORD).(string),
63+
Password: keystonePasswordObj.(string),
5364
Domain: setting.KeystoneDefaultDomain,
5465
Server: setting.KeystoneURL,
5566
}
@@ -70,11 +81,11 @@ func validateCurrentToken(c *middleware.Context) (bool, error) {
7081
return false, nil
7182
}
7283

73-
expiration_string := c.Session.Get(SESS_TOKEN_EXPIRATION).(string)
74-
if expiration_string == "" {
84+
expiration_obj := c.Session.Get(SESS_TOKEN_EXPIRATION)
85+
if expiration_obj == nil || expiration_obj.(string) == "" {
7586
return false, nil
7687
}
77-
expiration, err := time.Parse(time.RFC3339, c.Session.Get(SESS_TOKEN_EXPIRATION).(string))
88+
expiration, err := time.Parse(time.RFC3339, expiration_obj.(string))
7889
if err != nil {
7990
return false, err
8091
}
@@ -101,7 +112,12 @@ func GetToken(c *middleware.Context) (string, error) {
101112
var err error
102113
valid, err := validateCurrentToken(c)
103114
if valid {
104-
return c.Session.Get(SESS_TOKEN).(string), nil
115+
116+
var sessionTokenObj interface{}
117+
if sessionTokenObj = c.Session.Get(SESS_TOKEN); sessionTokenObj == nil {
118+
return "", errors.New("Session timed out trying to get token")
119+
}
120+
return sessionTokenObj.(string), nil
105121
}
106122
if token, err = getNewToken(c); err != nil {
107123
return "", err

pkg/api/keystone/keystone_requests.go

Lines changed: 110 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,16 @@ package keystone
22

33
import (
44
"bytes"
5+
"crypto/tls"
6+
"crypto/x509"
57
"encoding/json"
68
"errors"
9+
"io/ioutil"
710
"net/http"
11+
12+
"fmt"
13+
"github.com/grafana/grafana/pkg/log"
14+
"github.com/grafana/grafana/pkg/setting"
815
)
916

1017
///////////////////////
@@ -18,7 +25,7 @@ type auth_request_struct struct {
1825

1926
type auth_struct struct {
2027
Identity auth_identity_struct `json:"identity"`
21-
Scope string `json:"scope"`
28+
Scope string `json:"scope,omitempty"`
2229
}
2330

2431
type scoped_auth_token_request_struct struct {
@@ -30,15 +37,13 @@ type scoped_auth_password_request_struct struct {
3037
}
3138

3239
type scoped_auth_token_struct struct {
33-
Nocatalog bool `json:"nocatalog"`
34-
Identity auth_scoped_identity_struct `json:"identity"`
35-
Scope auth_scope_struct `json:"scope"`
40+
Identity auth_scoped_identity_struct `json:"identity"`
41+
Scope auth_scope_struct `json:"scope"`
3642
}
3743

3844
type scoped_auth_password_struct struct {
39-
Nocatalog bool `json:"nocatalog"`
40-
Identity auth_identity_struct `json:"identity"`
41-
Scope auth_scope_struct `json:"scope"`
45+
Identity auth_identity_struct `json:"identity"`
46+
Scope auth_scope_struct `json:"scope"`
4247
}
4348

4449
type auth_scoped_identity_struct struct {
@@ -127,6 +132,7 @@ type Auth_data struct {
127132

128133
func AuthenticateScoped(data *Auth_data) error {
129134
if data.UnscopedToken != "" {
135+
log.Trace("AuthenticateScoped() with token")
130136
var auth_post scoped_auth_token_request_struct
131137
auth_post.Auth.Identity.Methods = []string{"token"}
132138
auth_post.Auth.Identity.Token.Id = data.UnscopedToken
@@ -136,7 +142,7 @@ func AuthenticateScoped(data *Auth_data) error {
136142
return authenticate(data, b)
137143
} else {
138144
var auth_post scoped_auth_password_request_struct
139-
auth_post.Auth.Nocatalog = true
145+
log.Trace("AuthenticateScoped() with password")
140146
auth_post.Auth.Identity.Methods = []string{"password"}
141147
auth_post.Auth.Identity.Password.User.Name = data.Username
142148
auth_post.Auth.Identity.Password.User.Password = data.Password
@@ -149,6 +155,7 @@ func AuthenticateScoped(data *Auth_data) error {
149155
}
150156

151157
func AuthenticateUnscoped(data *Auth_data) error {
158+
log.Trace("AuthenticateUnscoped()")
152159
var auth_post auth_request_struct
153160
auth_post.Auth.Scope = "unscoped"
154161
auth_post.Auth.Identity.Methods = []string{"password"}
@@ -159,22 +166,43 @@ func AuthenticateUnscoped(data *Auth_data) error {
159166

160167
return authenticate(data, b)
161168
}
162-
163169
func authenticate(data *Auth_data, b []byte) error {
164-
request, err := http.NewRequest("POST", data.Server+"/v3/auth/tokens", bytes.NewBuffer(b))
170+
auth_url := data.Server + "/v3/auth/tokens?nocatalog"
171+
172+
log.Debug("Authentication request to URL: %s", auth_url)
173+
174+
log.Debug("Authentication request body: \n%s", anonymisePasswordsTokens(data, b))
175+
176+
request, err := http.NewRequest("POST", auth_url, bytes.NewBuffer(b))
165177
if err != nil {
166178
return err
167179
}
168180

169-
client := &http.Client{}
170-
resp, err := client.Do(request)
181+
resp, err := GetHttpClient().Do(request)
171182
if err != nil {
172183
return err
173-
} else if resp.StatusCode != 201 {
184+
}
185+
defer resp.Body.Close()
186+
187+
if resp.StatusCode != 201 {
174188
return errors.New("Keystone authentication failed: " + resp.Status)
175189
}
176190

177-
decoder := json.NewDecoder(resp.Body)
191+
var decoder *json.Decoder
192+
193+
if log.IsDebug() {
194+
buf := new(bytes.Buffer)
195+
buf.ReadFrom(resp.Body)
196+
strBody := buf.Bytes()
197+
198+
log.Debug("Authentication response: \n%s", strBody)
199+
200+
bodyReader := bytes.NewBufferString(fmt.Sprintf("%s", strBody))
201+
decoder = json.NewDecoder(bodyReader)
202+
} else {
203+
decoder = json.NewDecoder(resp.Body)
204+
}
205+
178206
var auth_response auth_response_struct
179207
err = decoder.Decode(&auth_response)
180208
if err != nil {
@@ -188,6 +216,20 @@ func authenticate(data *Auth_data, b []byte) error {
188216
return nil
189217
}
190218

219+
func anonymisePasswordsTokens(data *Auth_data, json []byte) []byte {
220+
anonJson := json
221+
if data.Password != "" {
222+
anonJson = bytes.Replace(anonJson, []byte("\"password\":\""+data.Password+"\""),
223+
[]byte("\"password\":\"********\""), -1)
224+
}
225+
if data.UnscopedToken != "" {
226+
anonJson = bytes.Replace(anonJson, []byte("\"token\":{\"id\":\""+data.UnscopedToken+"\""),
227+
[]byte("\"token\":{\"id\":\"****************\""), -1)
228+
}
229+
230+
return anonJson
231+
}
232+
191233
// Projects Section
192234
type Projects_data struct {
193235
Token string
@@ -197,21 +239,39 @@ type Projects_data struct {
197239
}
198240

199241
func GetProjects(data *Projects_data) error {
242+
log.Info("Authentication request to URL: %s", data.Server+"/v3/auth/projects")
243+
200244
request, err := http.NewRequest("GET", data.Server+"/v3/auth/projects", nil)
201245
if err != nil {
202246
return err
203247
}
204248
request.Header.Add("X-Auth-Token", data.Token)
205249

206-
client := &http.Client{}
207-
resp, err := client.Do(request)
250+
resp, err := GetHttpClient().Do(request)
208251
if err != nil {
209252
return err
210-
} else if resp.StatusCode != 200 {
253+
}
254+
defer resp.Body.Close()
255+
256+
if resp.StatusCode != 200 {
211257
return errors.New("Keystone project-list failed: " + resp.Status)
212258
}
213259

214-
decoder := json.NewDecoder(resp.Body)
260+
var decoder *json.Decoder
261+
262+
if log.IsDebug() {
263+
buf := new(bytes.Buffer)
264+
buf.ReadFrom(resp.Body)
265+
strBody := buf.Bytes()
266+
267+
log.Debug("Projects response: \n%s", strBody)
268+
269+
bodyReader := bytes.NewBufferString(fmt.Sprintf("%s", strBody))
270+
decoder = json.NewDecoder(bodyReader)
271+
} else {
272+
decoder = json.NewDecoder(resp.Body)
273+
}
274+
215275
var project_response project_response_struct
216276
err = decoder.Decode(&project_response)
217277
if err != nil {
@@ -224,3 +284,35 @@ func GetProjects(data *Projects_data) error {
224284
}
225285
return nil
226286
}
287+
288+
// From https://golang.org/pkg/net/http:
289+
// "Clients and Transports are safe for concurrent use by multiple goroutines and for efficiency should only be created once and re-used."
290+
var client *http.Client
291+
292+
func GetHttpClient() *http.Client {
293+
if client != nil {
294+
return client
295+
} else {
296+
var certPool *x509.CertPool
297+
if pemfile := setting.KeystoneRootCAPEMFile; pemfile != "" {
298+
certPool = x509.NewCertPool()
299+
pemFileContent, err := ioutil.ReadFile(pemfile)
300+
if err != nil {
301+
panic(err)
302+
}
303+
if !certPool.AppendCertsFromPEM(pemFileContent) {
304+
log.Error(3, "Failed to load any certificates from Root CA PEM file %s", pemfile)
305+
} else {
306+
log.Info("Successfully loaded certificate(s) from %s", pemfile)
307+
}
308+
}
309+
tr := &http.Transport{
310+
TLSClientConfig: &tls.Config{RootCAs: certPool,
311+
InsecureSkipVerify: !setting.KeystoneVerifySSLCert},
312+
}
313+
tr.Proxy = http.ProxyFromEnvironment
314+
315+
client = &http.Client{Transport: tr}
316+
return client
317+
}
318+
}

0 commit comments

Comments
 (0)