8e5b17cf13
Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
208 lines
5.5 KiB
Go
208 lines
5.5 KiB
Go
/*
|
|
Copyright 2014 The Kubernetes 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 discovery
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/square/go-jose"
|
|
)
|
|
|
|
type mockTokenLoader struct {
|
|
tokenID string
|
|
token string
|
|
}
|
|
|
|
func (tl *mockTokenLoader) LoadAndLookup(tokenID string) (string, error) {
|
|
if tokenID == tl.tokenID {
|
|
return tl.token, nil
|
|
}
|
|
return "", errors.New(fmt.Sprintf("invalid token: %s", tokenID))
|
|
}
|
|
|
|
const mockEndpoint1 = "https://192.168.1.5:8080"
|
|
const mockEndpoint2 = "https://192.168.1.6:8080"
|
|
|
|
type mockEndpointsLoader struct {
|
|
}
|
|
|
|
func (el *mockEndpointsLoader) LoadList() ([]string, error) {
|
|
return []string{mockEndpoint1, mockEndpoint2}, nil
|
|
}
|
|
|
|
const mockCA = "---BEGIN------END---DUMMYDATA"
|
|
|
|
type mockCALoader struct {
|
|
}
|
|
|
|
func (cl *mockCALoader) LoadPEM() (string, error) {
|
|
return mockCA, nil
|
|
}
|
|
|
|
const mockTokenID = "AAAAAA"
|
|
const mockToken = "9537434E638E4378"
|
|
|
|
const mockTokenIDCustom = "SHAREDSECRET"
|
|
const mockTokenCustom = "VERYSECRETTOKEN"
|
|
|
|
func TestClusterInfoIndex(t *testing.T) {
|
|
longToken := strings.Repeat("a", 1000)
|
|
tests := map[string]struct {
|
|
tokenID string // token ID the mock loader will use
|
|
token string // token the mock loader will use
|
|
reqTokenID string // token ID the will request with
|
|
reqToken string // token the caller will validate response with
|
|
expStatus int
|
|
expVerifyFailure bool
|
|
}{
|
|
"no token": {
|
|
tokenID: mockTokenID,
|
|
token: mockToken,
|
|
reqTokenID: "",
|
|
reqToken: "",
|
|
expStatus: http.StatusForbidden,
|
|
},
|
|
"valid token ID": {
|
|
tokenID: mockTokenID,
|
|
token: mockToken,
|
|
reqTokenID: mockTokenID,
|
|
reqToken: mockToken,
|
|
expStatus: http.StatusOK,
|
|
},
|
|
"valid arbitrary string token": {
|
|
tokenID: mockTokenIDCustom,
|
|
token: mockTokenCustom,
|
|
reqTokenID: mockTokenIDCustom,
|
|
reqToken: mockTokenCustom,
|
|
expStatus: http.StatusOK,
|
|
},
|
|
"valid arbitrary long string token": {
|
|
tokenID: "LONGTOKENTEST",
|
|
token: longToken,
|
|
reqTokenID: "LONGTOKENTEST",
|
|
reqToken: longToken,
|
|
expStatus: http.StatusOK,
|
|
},
|
|
"invalid token ID": {
|
|
tokenID: mockTokenID,
|
|
token: mockToken,
|
|
reqTokenID: "BADTOKENID",
|
|
reqToken: mockToken,
|
|
expStatus: http.StatusForbidden,
|
|
},
|
|
"invalid token": {
|
|
tokenID: mockTokenID,
|
|
token: mockToken,
|
|
reqTokenID: mockTokenID,
|
|
reqToken: "badtoken",
|
|
expStatus: http.StatusOK,
|
|
expVerifyFailure: true,
|
|
},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Logf("Running test: %s", name)
|
|
tokenLoader := &mockTokenLoader{test.tokenID, test.token}
|
|
// Create a request to pass to our handler. We don't have any query parameters for now, so we'll
|
|
// pass 'nil' as the third parameter.
|
|
url := "/cluster-info/v1/"
|
|
if test.tokenID != "" {
|
|
url = fmt.Sprintf("%s?token-id=%s", url, test.reqTokenID)
|
|
}
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
rr := httptest.NewRecorder()
|
|
handler := &ClusterInfoHandler{
|
|
tokenLoader: tokenLoader,
|
|
caLoader: &mockCALoader{},
|
|
endpointsLoader: &mockEndpointsLoader{},
|
|
}
|
|
|
|
handler.ServeHTTP(rr, req)
|
|
|
|
if status := rr.Code; status != test.expStatus {
|
|
t.Errorf("handler returned wrong status code: got %v want %v",
|
|
status, test.expStatus)
|
|
continue
|
|
}
|
|
|
|
// If we were expecting valid status validate the body:
|
|
if test.expStatus == http.StatusOK {
|
|
var ci ClusterInfo
|
|
|
|
body := string(rr.Body.Bytes())
|
|
|
|
// Parse the JSON web signature:
|
|
jws, err := jose.ParseSigned(body)
|
|
if err != nil {
|
|
t.Errorf("Error parsing JWS from request body: %s", err)
|
|
continue
|
|
}
|
|
|
|
// Now we can verify the signature on the payload. An error here would
|
|
// indicate the the message failed to verify, e.g. because the signature was
|
|
// broken or the message was tampered with.
|
|
var clusterInfoBytes []byte
|
|
hmacTestKey := []byte(test.reqToken)
|
|
clusterInfoBytes, err = jws.Verify(hmacTestKey)
|
|
|
|
if test.expVerifyFailure {
|
|
if err == nil {
|
|
t.Errorf("Signature verification did not fail as expected.")
|
|
}
|
|
// We are done the test here either way.
|
|
continue
|
|
}
|
|
|
|
if err != nil {
|
|
t.Errorf("Error verifing signature: %s", err)
|
|
continue
|
|
}
|
|
|
|
err = json.Unmarshal(clusterInfoBytes, &ci)
|
|
if err != nil {
|
|
t.Errorf("Unable to unmarshall payload to JSON: error=%s body=%s", err, rr.Body.String())
|
|
continue
|
|
}
|
|
if len(ci.Endpoints) != 2 {
|
|
t.Errorf("Expected 2 endpoints, got: %d", len(ci.Endpoints))
|
|
}
|
|
if mockEndpoint1 != ci.Endpoints[0] {
|
|
t.Errorf("Unexpected endpoint: %s", ci.Endpoints[0])
|
|
}
|
|
if mockEndpoint2 != ci.Endpoints[1] {
|
|
t.Errorf("Unexpected endpoint: %s", ci.Endpoints[1])
|
|
}
|
|
|
|
if len(ci.CertificateAuthorities) != 1 {
|
|
t.Errorf("Expected 1 root certificate, got: %d", len(ci.CertificateAuthorities))
|
|
}
|
|
if ci.CertificateAuthorities[0] != mockCA {
|
|
t.Errorf("Expected CA: %s, got: %s", mockCA, ci.CertificateAuthorities[0])
|
|
}
|
|
}
|
|
}
|
|
}
|