Files
teleport/lib/client/keystore_test.go
T
Brian Joerger 488af75797 Client store generalization (#19420)
- Add a generalized client store made up of a key, profile, and trusted certs store. Each sub store can support different backends (~/.tsh, identity_file, in-memory). 
- Replace custom identity file handling with in-memory client store.
- Fix issues with trusted certs handling.
2023-01-06 01:34:04 +00:00

291 lines
8.0 KiB
Go

/*
Copyright 2016-2020 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 client
import (
"fmt"
"os"
"path/filepath"
"testing"
"github.com/gravitational/trace"
"github.com/stretchr/testify/require"
"golang.org/x/crypto/ssh"
"github.com/gravitational/teleport/lib/utils/cert"
)
func newTestFSKeyStore(t *testing.T) *FSKeyStore {
fsKeyStore := NewFSKeyStore(t.TempDir())
return fsKeyStore
}
func testEachKeyStore(t *testing.T, testFunc func(t *testing.T, keyStore KeyStore)) {
t.Run("FS", func(t *testing.T) {
testFunc(t, newTestFSKeyStore(t))
})
t.Run("Mem", func(t *testing.T) {
testFunc(t, NewMemKeyStore())
})
}
func TestKeyStore(t *testing.T) {
t.Parallel()
s := newTestAuthority(t)
testEachKeyStore(t, func(t *testing.T, keyStore KeyStore) {
t.Parallel()
// create a test key
idx := KeyIndex{"test.proxy.com", "test-user", "root"}
key := s.makeSignedKey(t, idx, false)
// add the test key to the memory store
err := keyStore.AddKey(key)
require.NoError(t, err)
// check that the key exists in the store and is the same,
// except the key's trusted certs should be empty, to be
// filled in by a trusted certs store.
retrievedKey, err := keyStore.GetKey(idx, WithAllCerts...)
require.NoError(t, err)
key.TrustedCerts = nil
require.Equal(t, key, retrievedKey)
// Delete just the db cert, reload & verify it's gone
err = keyStore.DeleteUserCerts(idx, WithDBCerts{})
require.NoError(t, err)
retrievedKey, err = keyStore.GetKey(idx, WithSSHCerts{}, WithDBCerts{})
require.NoError(t, err)
expectKey := key.Copy()
expectKey.DBTLSCerts = make(map[string][]byte)
require.Equal(t, expectKey, retrievedKey)
// check for the key, now without cluster name
retrievedKey, err = keyStore.GetKey(KeyIndex{idx.ProxyHost, idx.Username, ""})
require.NoError(t, err)
expectKey.ClusterName = ""
expectKey.Cert = nil
require.Equal(t, expectKey, retrievedKey)
// delete the key
err = keyStore.DeleteKey(idx)
require.NoError(t, err)
// check that the key doesn't exist in the store
retrievedKey, err = keyStore.GetKey(idx)
require.Error(t, err)
require.True(t, trace.IsNotFound(err))
require.Nil(t, retrievedKey)
// Delete non-existing
err = keyStore.DeleteKey(idx)
require.Error(t, err)
require.True(t, trace.IsNotFound(err))
})
}
func TestListKeys(t *testing.T) {
t.Parallel()
auth := newTestAuthority(t)
testEachKeyStore(t, func(t *testing.T, keyStore KeyStore) {
t.Parallel()
const keyNum = 5
// add 5 keys for "bob"
keys := make([]Key, keyNum)
for i := 0; i < keyNum; i++ {
idx := KeyIndex{fmt.Sprintf("host-%v", i), "bob", "root"}
key := auth.makeSignedKey(t, idx, false)
require.NoError(t, keyStore.AddKey(key))
keys[i] = *key
}
// add 1 key for "sam"
samIdx := KeyIndex{"sam.host", "sam", "root"}
samKey := auth.makeSignedKey(t, samIdx, false)
require.NoError(t, keyStore.AddKey(samKey))
// read all bob keys:
for i := 0; i < keyNum; i++ {
key, err := keyStore.GetKey(keys[i].KeyIndex, WithSSHCerts{}, WithDBCerts{})
require.NoError(t, err)
key.TrustedCerts = keys[i].TrustedCerts
require.Equal(t, &keys[i], key)
}
// read sam's key and make sure it's the same:
skey, err := keyStore.GetKey(samIdx, WithSSHCerts{})
require.NoError(t, err)
require.Equal(t, samKey.Cert, skey.Cert)
require.Equal(t, samKey.MarshalSSHPublicKey(), skey.MarshalSSHPublicKey())
})
}
func TestGetCertificates(t *testing.T) {
t.Parallel()
auth := newTestAuthority(t)
testEachKeyStore(t, func(t *testing.T, keyStore KeyStore) {
const keyNum = 3
// add keys for 3 different clusters with the same user and proxy.
keys := make([]Key, keyNum)
certs := make([]*ssh.Certificate, keyNum)
var proxy = "proxy.example.com"
var user = "bob"
for i := 0; i < keyNum; i++ {
idx := KeyIndex{proxy, user, fmt.Sprintf("cluster-%v", i)}
key := auth.makeSignedKey(t, idx, false)
err := keyStore.AddKey(key)
require.NoError(t, err)
keys[i] = *key
certs[i], err = key.SSHCert()
require.NoError(t, err)
}
retrievedCerts, err := keyStore.GetSSHCertificates(proxy, user)
require.NoError(t, err)
require.ElementsMatch(t, certs, retrievedCerts)
})
}
func TestDeleteAll(t *testing.T) {
t.Parallel()
auth := newTestAuthority(t)
testEachKeyStore(t, func(t *testing.T, keyStore KeyStore) {
// generate keys
idxFoo := KeyIndex{"proxy.example.com", "foo", "root"}
keyFoo := auth.makeSignedKey(t, idxFoo, false)
idxBar := KeyIndex{"proxy.example.com", "bar", "root"}
keyBar := auth.makeSignedKey(t, idxBar, false)
// add keys
err := keyStore.AddKey(keyFoo)
require.NoError(t, err)
err = keyStore.AddKey(keyBar)
require.NoError(t, err)
// check keys exist
_, err = keyStore.GetKey(idxFoo)
require.NoError(t, err)
_, err = keyStore.GetKey(idxBar)
require.NoError(t, err)
// delete all keys
err = keyStore.DeleteKeys()
require.NoError(t, err)
// verify keys are gone
_, err = keyStore.GetKey(idxFoo)
require.True(t, trace.IsNotFound(err))
_, err = keyStore.GetKey(idxBar)
require.Error(t, err)
})
}
// TestCheckKey makes sure Teleport clients can load non-RSA algorithms in
// normal operating mode.
func TestCheckKey(t *testing.T) {
t.Parallel()
auth := newTestAuthority(t)
testEachKeyStore(t, func(t *testing.T, keyStore KeyStore) {
idx := KeyIndex{"host.a", "bob", "root"}
key := auth.makeSignedKey(t, idx, false)
// Swap out the key with a ECDSA SSH key.
ellipticCertificate, _, err := cert.CreateEllipticCertificate("foo", ssh.UserCert)
require.NoError(t, err)
key.Cert = ssh.MarshalAuthorizedKey(ellipticCertificate)
err = keyStore.AddKey(key)
require.NoError(t, err)
_, err = keyStore.GetKey(idx)
require.NoError(t, err)
})
}
// TestCheckKeyFIPS makes sure Teleport clients don't load invalid
// certificates while in FIPS mode.
func TestCheckKeyFIPS(t *testing.T) {
t.Parallel()
auth := newTestAuthority(t)
// This test only runs in FIPS mode.
if !isFIPS() {
t.Skip("This test only runs in FIPS mode.")
}
testEachKeyStore(t, func(t *testing.T, keyStore KeyStore) {
idx := KeyIndex{"host.a", "bob", "root"}
key := auth.makeSignedKey(t, idx, false)
// Swap out the key with a ECDSA SSH key.
ellipticCertificate, _, err := cert.CreateEllipticCertificate("foo", ssh.UserCert)
require.NoError(t, err)
key.Cert = ssh.MarshalAuthorizedKey(ellipticCertificate)
err = keyStore.AddKey(key)
require.NoError(t, err)
// Should return trace.BadParameter error because only RSA keys are supported.
_, err = keyStore.GetKey(idx)
require.True(t, trace.IsBadParameter(err))
})
}
func TestAddKey_withoutSSHCert(t *testing.T) {
t.Parallel()
auth := newTestAuthority(t)
keyStore := newTestFSKeyStore(t)
// without ssh cert, db certs only
idx := KeyIndex{"host.a", "bob", "root"}
key := auth.makeSignedKey(t, idx, false)
key.Cert = nil
require.NoError(t, keyStore.AddKey(key))
// ssh cert path should NOT exist
sshCertPath := keyStore.sshCertPath(key.KeyIndex)
_, err := os.Stat(sshCertPath)
require.ErrorIs(t, err, os.ErrNotExist)
// check db certs
keyCopy, err := keyStore.GetKey(idx, WithDBCerts{})
require.NoError(t, err)
require.Len(t, keyCopy.DBTLSCerts, 1)
}
func TestConfigDirNotDeleted(t *testing.T) {
t.Parallel()
auth := newTestAuthority(t)
keyStore := newTestFSKeyStore(t)
idx := KeyIndex{"host.a", "bob", "root"}
keyStore.AddKey(auth.makeSignedKey(t, idx, false))
configPath := filepath.Join(keyStore.KeyDir, "config")
require.NoError(t, os.Mkdir(configPath, 0700))
require.NoError(t, keyStore.DeleteKeys())
require.DirExists(t, configPath)
require.NoDirExists(t, filepath.Join(keyStore.KeyDir, "keys"))
}