Skip to content
Merged
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
2 changes: 1 addition & 1 deletion pkg/k8shandler/common_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package k8shandler

import (
"fmt"
"reflect"
"testing"
"fmt"

v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
Expand Down
2 changes: 2 additions & 0 deletions pkg/k8shandler/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ const (
elasticsearchCertsPath = "/etc/openshift/elasticsearch/secret"
elasticsearchConfigPath = "/usr/share/java/elasticsearch/config"
heapDumpLocation = "/elasticsearch/persistent/heapdump.hprof"

k8sTokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
)

func kibanaIndexMode(mode string) (string, error) {
Expand Down
131 changes: 131 additions & 0 deletions pkg/k8shandler/elasticsearch.go
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,40 @@ func updateIndexReplicas(clusterName, namespace string, client client.Client, in
return (payload.StatusCode == 200 && acknowledged), payload.Error
}

func ensureTokenHeader(header http.Header) http.Header {

if header == nil {
header = map[string][]string{}
}

if saToken, ok := readSAToken(k8sTokenFile); ok {
header["x-forwarded-access-token"] = []string{
saToken,
}
}

return header
}

// we want to read each time so that we can be sure to have the most up to date
// token in the case where our perms change and a new token is mounted
func readSAToken(tokenFile string) (string, bool) {
// read from /var/run/secrets/kubernetes.io/serviceaccount/token
token, err := ioutil.ReadFile(tokenFile)

if err != nil {
logrus.Errorf("Unable to read auth token from file [%s]: %v", tokenFile, err)
return "", false
}

if len(token) == 0 {
logrus.Errorf("Unable to read auth token from file [%s]: empty token", tokenFile)
return "", false
}

return string(token), true
}

// This will curl the ES service and provide the certs required for doing so
// it will also return the http and string response
func curlESService(clusterName, namespace string, payload *esCurlStruct, client client.Client) {
Expand Down Expand Up @@ -670,13 +704,86 @@ func curlESService(clusterName, namespace string, payload *esCurlStruct, client
return
}

request.Header = ensureTokenHeader(request.Header)
httpClient := getClient(clusterName, namespace, client)
resp, err := httpClient.Do(request)

if resp != nil {
// TODO: eventually remove after all ES images have been updated to use SA token auth for EO?
if resp.StatusCode == http.StatusForbidden ||
resp.StatusCode == http.StatusUnauthorized {
// if we get a 401 that means that we couldn't read from the token and provided
// no header.
// if we get a 403 that means the ES cluster doesn't allow us to use
// our SA token.
// in both cases, try the old way.

// Not sure why, but just trying to reuse the request with the old client
// resulted in a 400 every time. Doing it this way got a 200 response as expected.
curlESServiceOldClient(clusterName, namespace, payload, client)
return
}

payload.StatusCode = resp.StatusCode
payload.ResponseBody = getMapFromBody(resp.Body)
}

payload.Error = err
}

func curlESServiceOldClient(clusterName, namespace string, payload *esCurlStruct, client client.Client) {

urlString := fmt.Sprintf("https://%s.%s.svc:9200/%s", clusterName, namespace, payload.URI)
urlURL, err := url.Parse(urlString)

if err != nil {
logrus.Warnf("Unable to parse URL %v: %v", urlString, err)
return
}

request := &http.Request{
Method: payload.Method,
URL: urlURL,
}

switch payload.Method {
case http.MethodGet:
// no more to do to request...
case http.MethodPost:
if payload.RequestBody != "" {
// add to the request
request.Header = map[string][]string{
"Content-Type": []string{
"application/json",
},
}
request.Body = ioutil.NopCloser(bytes.NewReader([]byte(payload.RequestBody)))
}

case http.MethodPut:
if payload.RequestBody != "" {
// add to the request
request.Header = map[string][]string{
"Content-Type": []string{
"application/json",
},
}
request.Body = ioutil.NopCloser(bytes.NewReader([]byte(payload.RequestBody)))
}

default:
// unsupported method -- do nothing
return
}

httpClient := getOldClient(clusterName, namespace, client)
resp, err := httpClient.Do(request)

if resp != nil {
payload.StatusCode = resp.StatusCode
payload.ResponseBody = getMapFromBody(resp.Body)
}

payload.Error = err
}

Expand Down Expand Up @@ -725,6 +832,30 @@ func getClientCertificates(clusterName, namespace string) []tls.Certificate {

func getClient(clusterName, namespace string, client client.Client) *http.Client {

// http.Transport sourced from go 1.10.7
return &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
// we cannot rely on certificates as they may rotate and therefore would be invalid
// since ES listens on https and presents a server cert, we need to not verify it
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
}

func getOldClient(clusterName, namespace string, client client.Client) *http.Client {

// get the contents of the secret
extractSecret(clusterName, namespace, client)

Expand Down
42 changes: 42 additions & 0 deletions pkg/k8shandler/elasticsearch_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package k8shandler

import (
"testing"
)

func TestHeaderGenEmptyToken(t *testing.T) {
tokenFile := "../../test/files/emptyToken"

_, ok := readSAToken(tokenFile)

if ok {
t.Errorf("Expected to be unable to read file [%s] due to empty file but could", tokenFile)
}
}

func TestHeaderGenWithToken(t *testing.T) {
tokenFile := "../../test/files/testToken"

expected := "test\n"

actual, ok := readSAToken(tokenFile)

if !ok {
t.Errorf("Expected to be able to read file [%s] but couldn't", tokenFile)

} else {
if actual != expected {
t.Errorf("Expected %q but got %q", expected, actual)
}
}
}

func TestHeaderGenWithNoToken(t *testing.T) {
tokenFile := "../../test/files/errorToken"

_, ok := readSAToken(tokenFile)

if ok {
t.Errorf("Expected to be unable to read file [%s]", tokenFile)
}
}
Empty file added test/files/emptyToken
Empty file.
1 change: 1 addition & 0 deletions test/files/testToken
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test