forked from mystiq/dex
Merge pull request #1835 from flant/kubernetes-client-keys-conflicts-fix
fix: Handle Kubernetes API conflicts properly for signing keys
This commit is contained in:
commit
28b2350cd2
2 changed files with 146 additions and 2 deletions
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -512,6 +513,7 @@ func (cli *client) UpdateKeys(updater func(old storage.Keys) (storage.Keys, erro
|
||||||
}
|
}
|
||||||
firstUpdate = true
|
firstUpdate = true
|
||||||
}
|
}
|
||||||
|
|
||||||
var oldKeys storage.Keys
|
var oldKeys storage.Keys
|
||||||
if !firstUpdate {
|
if !firstUpdate {
|
||||||
oldKeys = toStorageKeys(keys)
|
oldKeys = toStorageKeys(keys)
|
||||||
|
@ -521,12 +523,32 @@ func (cli *client) UpdateKeys(updater func(old storage.Keys) (storage.Keys, erro
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
newKeys := cli.fromStorageKeys(updated)
|
newKeys := cli.fromStorageKeys(updated)
|
||||||
if firstUpdate {
|
if firstUpdate {
|
||||||
return cli.post(resourceKeys, newKeys)
|
err = cli.post(resourceKeys, newKeys)
|
||||||
|
if err != nil && errors.Is(err, storage.ErrAlreadyExists) {
|
||||||
|
// We need to tolerate conflicts here in case of HA mode.
|
||||||
|
cli.logger.Debugf("Keys creation failed: %v. It is possible that keys have already been created by another dex instance.", err)
|
||||||
|
return errors.New("keys already created by another server instance")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
newKeys.ObjectMeta = keys.ObjectMeta
|
newKeys.ObjectMeta = keys.ObjectMeta
|
||||||
return cli.put(resourceKeys, keysName, newKeys)
|
|
||||||
|
err = cli.put(resourceKeys, keysName, newKeys)
|
||||||
|
if httpErr, ok := err.(httpError); ok {
|
||||||
|
// We need to tolerate conflicts here in case of HA mode.
|
||||||
|
// Dex instances run keys rotation at the same time because they use SigningKey.nextRotation CR field as a trigger.
|
||||||
|
if httpErr.StatusCode() == http.StatusConflict {
|
||||||
|
cli.logger.Debugf("Keys rotation failed: %v. It is possible that keys have already been rotated by another dex instance.", err)
|
||||||
|
return errors.New("keys already rotated by another server instance")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *client) UpdateAuthRequest(id string, updater func(a storage.AuthRequest) (storage.AuthRequest, error)) error {
|
func (cli *client) UpdateAuthRequest(id string, updater func(a storage.AuthRequest) (storage.AuthRequest, error)) error {
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
package kubernetes
|
package kubernetes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -150,3 +155,120 @@ func TestURLFor(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue