-
Notifications
You must be signed in to change notification settings - Fork 576
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[cortex] Authentication Implementation and Timestamp Fix (#246)
* Create auth.go, auth_test.go and add copyright and license * Add helper function to create files for testing * Setup authentication tests and add first test for BasicAuth * Add BasicAuth and error definitions * Add additional tests for BasicAuth * Add file creation to pass basic auth tests * Add bearer token tests * Add bearer token authentication * Adjust timestamp to milliseconds and remove debugging print statement * Run make precommit and fix lint issues * Changed error strings to start with lowercase letter * Add explicit base time unit to timestamp * Moved basic auth validation and validation tests to config.go * Update comments for clarity
- Loading branch information
Eric Lee
authored
Aug 20, 2020
1 parent
a21b1d8
commit f2ec169
Showing
7 changed files
with
333 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
// Copyright The OpenTelemetry Authors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package cortex | ||
|
||
import ( | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
) | ||
|
||
// ErrFailedToReadFile occurs when a password / bearer token file exists, but could | ||
// not be read. | ||
var ErrFailedToReadFile = fmt.Errorf("failed to read password / bearer token file") | ||
|
||
// addBasicAuth sets the Authorization header for basic authentication using a username | ||
// and a password / password file. The header value is not changed if an Authorization | ||
// header already exists and no action is taken if the Exporter is not configured with | ||
// basic authorization credentials. | ||
func (e *Exporter) addBasicAuth(req *http.Request) error { | ||
// No need to add basic auth if it isn't provided or if the Authorization header is | ||
// already set. | ||
if _, exists := e.config.Headers["Authorization"]; exists { | ||
return nil | ||
} | ||
if e.config.BasicAuth == nil { | ||
return nil | ||
} | ||
|
||
username := e.config.BasicAuth["username"] | ||
|
||
// Use password from password file if it exists. | ||
passwordFile := e.config.BasicAuth["password_file"] | ||
if passwordFile != "" { | ||
file, err := ioutil.ReadFile(passwordFile) | ||
if err != nil { | ||
return ErrFailedToReadFile | ||
} | ||
password := string(file) | ||
req.SetBasicAuth(username, password) | ||
return nil | ||
} | ||
|
||
// Use provided password. | ||
password := e.config.BasicAuth["password"] | ||
req.SetBasicAuth(username, password) | ||
|
||
return nil | ||
} | ||
|
||
// addBearerTokenAuth sets the Authorization header for bearer tokens using a bearer token | ||
// string or a bearer token file. The header value is not changed if an Authorization | ||
// header already exists and no action is taken if the Exporter is not configured with | ||
// bearer token credentials. | ||
func (e *Exporter) addBearerTokenAuth(req *http.Request) error { | ||
// No need to add bearer token auth if the Authorization header is already set. | ||
if _, exists := e.config.Headers["Authorization"]; exists { | ||
return nil | ||
} | ||
|
||
// Use bearer token from bearer token file if it exists. | ||
if e.config.BearerTokenFile != "" { | ||
file, err := ioutil.ReadFile(e.config.BearerTokenFile) | ||
if err != nil { | ||
return ErrFailedToReadFile | ||
} | ||
bearerTokenString := "Bearer " + string(file) | ||
req.Header.Set("Authorization", bearerTokenString) | ||
return nil | ||
} | ||
|
||
// Otherwise, use bearer token field. | ||
if e.config.BearerToken != "" { | ||
bearerTokenString := "Bearer " + e.config.BearerToken | ||
req.Header.Set("Authorization", bearerTokenString) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
// Copyright The OpenTelemetry Authors | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package cortex | ||
|
||
import ( | ||
"encoding/base64" | ||
"io/ioutil" | ||
"net/http" | ||
"net/http/httptest" | ||
"os" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
// TestAuthentication checks whether http requests are properly authenticated with either | ||
// bearer tokens or basic authentication in the addHeaders method. | ||
func TestAuthentication(t *testing.T) { | ||
tests := []struct { | ||
testName string | ||
basicAuth map[string]string | ||
basicAuthPasswordFileContents []byte | ||
bearerToken string | ||
bearerTokenFile string | ||
bearerTokenFileContents []byte | ||
expectedAuthHeaderValue string | ||
expectedError error | ||
}{ | ||
{ | ||
testName: "Basic Auth with password", | ||
basicAuth: map[string]string{ | ||
"username": "TestUser", | ||
"password": "TestPassword", | ||
}, | ||
expectedAuthHeaderValue: "Basic " + base64.StdEncoding.EncodeToString( | ||
[]byte("TestUser:TestPassword"), | ||
), | ||
expectedError: nil, | ||
}, | ||
{ | ||
testName: "Basic Auth with password file", | ||
basicAuth: map[string]string{ | ||
"username": "TestUser", | ||
"password_file": "passwordFile", | ||
}, | ||
basicAuthPasswordFileContents: []byte("TestPassword"), | ||
expectedAuthHeaderValue: "Basic " + base64.StdEncoding.EncodeToString( | ||
[]byte("TestUser:TestPassword"), | ||
), | ||
expectedError: nil, | ||
}, | ||
{ | ||
testName: "Basic Auth with bad password file", | ||
basicAuth: map[string]string{ | ||
"username": "TestUser", | ||
"password_file": "missingPasswordFile", | ||
}, | ||
expectedAuthHeaderValue: "", | ||
expectedError: ErrFailedToReadFile, | ||
}, | ||
{ | ||
testName: "Bearer Token", | ||
bearerToken: "testToken", | ||
expectedAuthHeaderValue: "Bearer testToken", | ||
expectedError: nil, | ||
}, | ||
{ | ||
testName: "Bearer Token with bad bearer token file", | ||
bearerTokenFile: "missingBearerTokenFile", | ||
expectedAuthHeaderValue: "", | ||
expectedError: ErrFailedToReadFile, | ||
}, | ||
{ | ||
testName: "Bearer Token with bearer token file", | ||
bearerTokenFile: "bearerTokenFile", | ||
expectedAuthHeaderValue: "Bearer testToken", | ||
bearerTokenFileContents: []byte("testToken"), | ||
expectedError: nil, | ||
}, | ||
} | ||
for _, test := range tests { | ||
t.Run(test.testName, func(t *testing.T) { | ||
// Set up a test server that runs a handler function when it receives a http | ||
// request. The server writes the request's Authorization header to the | ||
// response body. | ||
handler := func(rw http.ResponseWriter, req *http.Request) { | ||
authHeaderValue := req.Header.Get("Authorization") | ||
_, err := rw.Write([]byte(authHeaderValue)) | ||
require.Nil(t, err) | ||
} | ||
server := httptest.NewServer(http.HandlerFunc(handler)) | ||
defer server.Close() | ||
|
||
// Create the necessary files for tests. | ||
if test.basicAuth != nil { | ||
passwordFile := test.basicAuth["password_file"] | ||
if passwordFile != "" && test.basicAuthPasswordFileContents != nil { | ||
filepath := "./" + test.basicAuth["password_file"] | ||
err := createFile(test.basicAuthPasswordFileContents, filepath) | ||
require.Nil(t, err) | ||
defer os.Remove(filepath) | ||
} | ||
} | ||
if test.bearerTokenFile != "" && test.bearerTokenFileContents != nil { | ||
filepath := "./" + test.bearerTokenFile | ||
err := createFile(test.bearerTokenFileContents, filepath) | ||
require.Nil(t, err) | ||
defer os.Remove(filepath) | ||
} | ||
|
||
// Create a HTTP request and add headers to it through an Exporter. Since the | ||
// Exporter has an empty Headers map, authentication methods will be called. | ||
exporter := Exporter{ | ||
Config{ | ||
BasicAuth: test.basicAuth, | ||
BearerToken: test.bearerToken, | ||
BearerTokenFile: test.bearerTokenFile, | ||
}, | ||
} | ||
req, err := http.NewRequest(http.MethodPost, server.URL, nil) | ||
require.Nil(t, err) | ||
err = exporter.addHeaders(req) | ||
|
||
// Verify the error and if the Authorization header was correctly set. | ||
if err != nil { | ||
require.Equal(t, err.Error(), test.expectedError.Error()) | ||
} else { | ||
require.Nil(t, test.expectedError) | ||
authHeaderValue := req.Header.Get("Authorization") | ||
require.Equal(t, authHeaderValue, test.expectedAuthHeaderValue) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
// createFile writes a file with a slice of bytes at a specified filepath. | ||
func createFile(bytes []byte, filepath string) error { | ||
err := ioutil.WriteFile(filepath, bytes, 0644) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.