2016-07-25 20:00:28 +00:00
|
|
|
package kubernetes
|
|
|
|
|
|
|
|
import (
|
2020-10-27 06:01:52 +00:00
|
|
|
"context"
|
2020-10-11 20:43:10 +00:00
|
|
|
"crypto/tls"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
2016-07-25 20:00:28 +00:00
|
|
|
"os"
|
2021-04-19 12:45:12 +00:00
|
|
|
"path/filepath"
|
2019-12-18 16:00:57 +00:00
|
|
|
"strings"
|
2016-07-25 20:00:28 +00:00
|
|
|
"testing"
|
|
|
|
|
2017-07-25 20:45:17 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2020-10-27 06:01:52 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2019-12-18 16:00:57 +00:00
|
|
|
"github.com/stretchr/testify/suite"
|
2018-09-03 06:44:44 +00:00
|
|
|
|
|
|
|
"github.com/dexidp/dex/storage"
|
|
|
|
"github.com/dexidp/dex/storage/conformance"
|
2016-07-25 20:00:28 +00:00
|
|
|
)
|
|
|
|
|
2021-04-19 12:45:12 +00:00
|
|
|
const kubeconfigPathVariableName = "DEX_KUBERNETES_CONFIG_PATH"
|
2016-10-23 14:58:28 +00:00
|
|
|
|
2019-12-18 16:00:57 +00:00
|
|
|
func TestStorage(t *testing.T) {
|
2021-04-19 12:45:12 +00:00
|
|
|
if os.Getenv(kubeconfigPathVariableName) == "" {
|
|
|
|
t.Skip(fmt.Sprintf("variable %q not set, skipping kubernetes storage tests\n", kubeconfigPathVariableName))
|
2019-12-18 16:00:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
suite.Run(t, new(StorageTestSuite))
|
|
|
|
}
|
|
|
|
|
|
|
|
type StorageTestSuite struct {
|
|
|
|
suite.Suite
|
|
|
|
client *client
|
|
|
|
}
|
|
|
|
|
2021-04-19 12:45:12 +00:00
|
|
|
func (s *StorageTestSuite) expandDir(dir string) string {
|
2021-05-12 16:13:32 +00:00
|
|
|
dir = strings.Trim(dir, `"`)
|
2021-04-19 12:45:12 +00:00
|
|
|
if strings.HasPrefix(dir, "~/") {
|
|
|
|
homedir, err := os.UserHomeDir()
|
|
|
|
s.Require().NoError(err)
|
2019-12-18 16:00:57 +00:00
|
|
|
|
2021-04-19 12:45:12 +00:00
|
|
|
dir = filepath.Join(homedir, strings.TrimPrefix(dir, "~/"))
|
|
|
|
}
|
|
|
|
return dir
|
2016-07-25 20:00:28 +00:00
|
|
|
}
|
|
|
|
|
2019-12-18 16:00:57 +00:00
|
|
|
func (s *StorageTestSuite) SetupTest() {
|
2021-04-19 12:45:12 +00:00
|
|
|
kubeconfigPath := s.expandDir(os.Getenv(kubeconfigPathVariableName))
|
2019-12-18 16:00:57 +00:00
|
|
|
|
2016-10-23 14:58:28 +00:00
|
|
|
config := Config{
|
2021-04-19 12:45:12 +00:00
|
|
|
KubeConfigFile: kubeconfigPath,
|
2016-07-25 20:00:28 +00:00
|
|
|
}
|
2019-12-18 16:00:57 +00:00
|
|
|
|
2016-11-22 23:35:46 +00:00
|
|
|
logger := &logrus.Logger{
|
|
|
|
Out: os.Stderr,
|
|
|
|
Formatter: &logrus.TextFormatter{DisableColors: true},
|
|
|
|
Level: logrus.DebugLevel,
|
|
|
|
}
|
2019-12-18 16:00:57 +00:00
|
|
|
|
2021-04-19 12:45:12 +00:00
|
|
|
kubeClient, err := config.open(logger, true)
|
2019-12-18 16:00:57 +00:00
|
|
|
s.Require().NoError(err)
|
|
|
|
|
2021-04-19 12:45:12 +00:00
|
|
|
s.client = kubeClient
|
2019-12-18 16:00:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *StorageTestSuite) TestStorage() {
|
|
|
|
newStorage := func() storage.Storage {
|
|
|
|
for _, resource := range []string{
|
|
|
|
resourceAuthCode,
|
|
|
|
resourceAuthRequest,
|
2020-01-16 15:55:07 +00:00
|
|
|
resourceDeviceRequest,
|
|
|
|
resourceDeviceToken,
|
2019-12-18 16:00:57 +00:00
|
|
|
resourceClient,
|
|
|
|
resourceRefreshToken,
|
|
|
|
resourceKeys,
|
|
|
|
resourcePassword,
|
|
|
|
} {
|
|
|
|
if err := s.client.deleteAll(resource); err != nil {
|
|
|
|
s.T().Fatalf("delete all %q failed: %v", resource, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return s.client
|
2016-07-25 20:00:28 +00:00
|
|
|
}
|
2019-12-18 16:00:57 +00:00
|
|
|
|
|
|
|
conformance.RunTests(s.T(), newStorage)
|
|
|
|
conformance.RunTransactionTests(s.T(), newStorage)
|
2016-07-25 20:00:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestURLFor(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
apiVersion, namespace, resource, name string
|
|
|
|
|
|
|
|
baseURL string
|
|
|
|
want string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"v1", "default", "pods", "a",
|
|
|
|
"https://k8s.example.com",
|
|
|
|
"https://k8s.example.com/api/v1/namespaces/default/pods/a",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"foo/v1", "default", "bar", "a",
|
|
|
|
"https://k8s.example.com",
|
|
|
|
"https://k8s.example.com/apis/foo/v1/namespaces/default/bar/a",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"foo/v1", "default", "bar", "a",
|
|
|
|
"https://k8s.example.com/",
|
|
|
|
"https://k8s.example.com/apis/foo/v1/namespaces/default/bar/a",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"foo/v1", "default", "bar", "a",
|
|
|
|
"https://k8s.example.com/",
|
|
|
|
"https://k8s.example.com/apis/foo/v1/namespaces/default/bar/a",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// no namespace
|
|
|
|
"foo/v1", "", "bar", "a",
|
|
|
|
"https://k8s.example.com",
|
|
|
|
"https://k8s.example.com/apis/foo/v1/bar/a",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
2016-10-13 23:50:20 +00:00
|
|
|
c := &client{baseURL: test.baseURL}
|
2016-07-25 20:00:28 +00:00
|
|
|
got := c.urlFor(test.apiVersion, test.namespace, test.resource, test.name)
|
|
|
|
if got != test.want {
|
|
|
|
t.Errorf("(&client{baseURL:%q}).urlFor(%q, %q, %q, %q): expected %q got %q",
|
|
|
|
test.baseURL,
|
|
|
|
test.apiVersion, test.namespace, test.resource, test.name,
|
|
|
|
test.want, got,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-10-11 20:43:10 +00:00
|
|
|
|
|
|
|
func TestUpdateKeys(t *testing.T) {
|
|
|
|
fakeUpdater := func(old storage.Keys) (storage.Keys, error) { return storage.Keys{}, nil }
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
updater func(old storage.Keys) (storage.Keys, error)
|
|
|
|
getResponseCode int
|
|
|
|
actionResponseCode int
|
|
|
|
wantErr bool
|
|
|
|
exactErr error
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"Create OK test",
|
|
|
|
fakeUpdater,
|
|
|
|
404,
|
|
|
|
201,
|
|
|
|
false,
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Update should be OK",
|
|
|
|
fakeUpdater,
|
|
|
|
200,
|
|
|
|
200,
|
|
|
|
false,
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Create conflict should be OK",
|
|
|
|
fakeUpdater,
|
|
|
|
404,
|
|
|
|
409,
|
|
|
|
true,
|
|
|
|
errors.New("keys already created by another server instance"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Update conflict should be OK",
|
|
|
|
fakeUpdater,
|
|
|
|
200,
|
|
|
|
409,
|
|
|
|
true,
|
|
|
|
errors.New("keys already rotated by another server instance"),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Client error is error",
|
|
|
|
fakeUpdater,
|
|
|
|
404,
|
|
|
|
500,
|
|
|
|
true,
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Client error during update is error",
|
|
|
|
fakeUpdater,
|
|
|
|
200,
|
|
|
|
500,
|
|
|
|
true,
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Get error is error",
|
|
|
|
fakeUpdater,
|
|
|
|
500,
|
|
|
|
200,
|
|
|
|
true,
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Updater error is error",
|
|
|
|
func(old storage.Keys) (storage.Keys, error) { return storage.Keys{}, fmt.Errorf("test") },
|
|
|
|
200,
|
|
|
|
201,
|
|
|
|
true,
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
client := newStatusCodesResponseTestClient(test.getResponseCode, test.actionResponseCode)
|
|
|
|
|
|
|
|
err := client.UpdateKeys(test.updater)
|
|
|
|
if err != nil {
|
|
|
|
if !test.wantErr {
|
|
|
|
t.Fatalf("Test %q: %v", test.name, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if test.exactErr != nil && test.exactErr.Error() != err.Error() {
|
|
|
|
t.Fatalf("Test %q: %v, wanted: %v", test.name, err, test.exactErr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func newStatusCodesResponseTestClient(getResponseCode, actionResponseCode int) *client {
|
|
|
|
s := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if r.Method == http.MethodGet {
|
|
|
|
w.WriteHeader(getResponseCode)
|
|
|
|
} else {
|
|
|
|
w.WriteHeader(actionResponseCode)
|
|
|
|
}
|
|
|
|
w.Write([]byte(`{}`)) // Empty json is enough, we will test only response codes here
|
|
|
|
}))
|
|
|
|
|
|
|
|
tr := &http.Transport{
|
|
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
|
|
}
|
|
|
|
return &client{
|
|
|
|
client: &http.Client{Transport: tr},
|
|
|
|
baseURL: s.URL,
|
|
|
|
logger: &logrus.Logger{
|
|
|
|
Out: os.Stderr,
|
|
|
|
Formatter: &logrus.TextFormatter{DisableColors: true},
|
|
|
|
Level: logrus.DebugLevel,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2020-10-27 06:01:52 +00:00
|
|
|
|
|
|
|
func TestRetryOnConflict(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
action func() error
|
|
|
|
exactErr string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"Timeout reached",
|
|
|
|
func() error { err := httpErr{status: 409}; return error(&err) },
|
2021-10-18 20:37:55 +00:00
|
|
|
"maximum timeout reached while retrying a conflicted request: Conflict: response from server \"\"",
|
2020-10-27 06:01:52 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"HTTP Error",
|
|
|
|
func() error { err := httpErr{status: 500}; return error(&err) },
|
|
|
|
" Internal Server Error: response from server \"\"",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"Error",
|
|
|
|
func() error { return errors.New("test") },
|
|
|
|
"test",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"OK",
|
|
|
|
func() error { return nil },
|
|
|
|
"",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, testCase := range tests {
|
|
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
|
|
err := retryOnConflict(context.TODO(), testCase.action)
|
|
|
|
if testCase.exactErr != "" {
|
|
|
|
require.EqualError(t, err, testCase.exactErr)
|
|
|
|
} else {
|
|
|
|
require.NoError(t, err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|