mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-31 20:39:37 +08:00
A few of our tests that run certificate rotation fail with an error similar to this: ``` can only switch to phase update_clients from init, the current phase is ``` This happens when a duplicated private key is returned and set as a new key during CA rotation. During the CA rotation, we look for duplicated keys https://github.com/gravitational/teleport/blob/962e5a25bc56b5aad05528867182a43cd53cb551/lib/auth/rotate.go#L218 and try to rotate all of them at one. Because some of our tests use still use `testauthority` that only has 4 keys there is a huge chance that the same key will be returned twice, which causes the issue. I swapped the logic to use a bigger pool with keys introduced in https://github.com/gravitational/teleport/pull/18750. I also removed the randomization part that, after some testing, I discovered was also causing some problems. We could probably refactor the code in many tests now to make it simpler, but I want to keep this change as simple as possible. Otherwise, backporting bigger changes to older branches takes a long time.
174 lines
4.3 KiB
Go
174 lines
4.3 KiB
Go
/*
|
|
Copyright 2015 Gravitational, Inc.
|
|
|
|
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 native
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gravitational/trace"
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"github.com/gravitational/teleport"
|
|
"github.com/gravitational/teleport/api/constants"
|
|
"github.com/gravitational/teleport/api/utils/keys"
|
|
)
|
|
|
|
var log = logrus.WithFields(logrus.Fields{
|
|
trace.Component: teleport.ComponentKeyGen,
|
|
})
|
|
|
|
// precomputedKeys is a queue of cached keys ready for usage.
|
|
var precomputedKeys = make(chan *rsa.PrivateKey, 25)
|
|
|
|
// startPrecomputeOnce is used to start the background task that precomputes key pairs.
|
|
var startPrecomputeOnce sync.Once
|
|
|
|
// GenerateKeyPair generates a new RSA key pair.
|
|
func GenerateKeyPair() ([]byte, []byte, error) {
|
|
priv, err := GeneratePrivateKey()
|
|
if err != nil {
|
|
return nil, nil, trace.Wrap(err)
|
|
}
|
|
return priv.PrivateKeyPEM(), priv.MarshalSSHPublicKey(), nil
|
|
}
|
|
|
|
// GeneratePrivateKey generates a new RSA private key.
|
|
func GeneratePrivateKey() (*keys.PrivateKey, error) {
|
|
rsaKey, err := getOrGenerateRSAPrivateKey()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
// We encode the private key in PKCS #1, ASN.1 DER form
|
|
// instead of PKCS #8 to maintain compatibility with some
|
|
// third party clients.
|
|
keyPEM := pem.EncodeToMemory(&pem.Block{
|
|
Type: keys.PKCS1PrivateKeyType,
|
|
Headers: nil,
|
|
Bytes: x509.MarshalPKCS1PrivateKey(rsaKey),
|
|
})
|
|
|
|
return keys.NewPrivateKey(rsaKey, keyPEM)
|
|
}
|
|
|
|
func GenerateRSAPrivateKey() (*rsa.PrivateKey, error) {
|
|
return getOrGenerateRSAPrivateKey()
|
|
}
|
|
|
|
func getOrGenerateRSAPrivateKey() (*rsa.PrivateKey, error) {
|
|
select {
|
|
case k := <-precomputedKeys:
|
|
return k, nil
|
|
default:
|
|
rsaKeyPair, err := generateRSAPrivateKey()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return rsaKeyPair, nil
|
|
}
|
|
}
|
|
|
|
func generateRSAPrivateKey() (*rsa.PrivateKey, error) {
|
|
return rsa.GenerateKey(rand.Reader, constants.RSAKeySize)
|
|
}
|
|
|
|
func precomputeKeys() {
|
|
const backoff = time.Second * 30
|
|
for {
|
|
rsaPrivateKey, err := generateRSAPrivateKey()
|
|
if err != nil {
|
|
log.WithError(err).Errorf("Failed to precompute key pair, retrying in %s (this might be a bug).", backoff)
|
|
time.Sleep(backoff)
|
|
}
|
|
|
|
precomputedKeys <- rsaPrivateKey
|
|
}
|
|
}
|
|
|
|
func precomputeTestKeys() {
|
|
testKeys, err := generateTestKeys()
|
|
if err != nil {
|
|
// Use only in tests. Safe to panic.
|
|
panic(err)
|
|
}
|
|
|
|
for {
|
|
for _, k := range testKeys {
|
|
precomputedKeys <- k
|
|
}
|
|
}
|
|
}
|
|
|
|
// testKeysNumber is the number of RSA keys generated in tests.
|
|
const testKeysNumber = 25
|
|
|
|
func generateTestKeys() ([]*rsa.PrivateKey, error) {
|
|
privateKeys := make([]*rsa.PrivateKey, 0, testKeysNumber)
|
|
keysChan := make(chan *rsa.PrivateKey)
|
|
errC := make(chan error)
|
|
|
|
go func() {
|
|
for i := 0; i < testKeysNumber; i++ {
|
|
// Generate each key in a separate goroutine to take advantage of
|
|
// multiple cores if possible.
|
|
go func() {
|
|
private, err := generateRSAPrivateKey()
|
|
if err != nil {
|
|
errC <- trace.Wrap(err)
|
|
return
|
|
}
|
|
keysChan <- private
|
|
}()
|
|
}
|
|
}()
|
|
|
|
for i := 0; i < testKeysNumber; i++ {
|
|
select {
|
|
case err := <-errC:
|
|
return nil, trace.Wrap(err)
|
|
case privKey := <-keysChan:
|
|
privateKeys = append(privateKeys, privKey)
|
|
}
|
|
}
|
|
|
|
return privateKeys, nil
|
|
}
|
|
|
|
// PrecomputeKeys sets this package into a mode where a small backlog of keys are
|
|
// computed in advance. This should only be enabled if large spikes in key computation
|
|
// are expected (e.g. in auth/proxy services). Safe to double-call.
|
|
func PrecomputeKeys() {
|
|
startPrecomputeOnce.Do(func() {
|
|
go precomputeKeys()
|
|
})
|
|
}
|
|
|
|
// PrecomputeTestKeys generates RSA keys and reuse them to reduce CPU usage. This method should
|
|
// only be in tests. Safe to call multiple times.
|
|
// This function takes *testing.M, so is only can be used from TestMain in tests.
|
|
func PrecomputeTestKeys(_ *testing.M) {
|
|
startPrecomputeOnce.Do(func() {
|
|
go precomputeTestKeys()
|
|
})
|
|
}
|