mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-17 17:40:30 +08:00
feat: Hardware Key Agent - Propagate contextual key info from key store to hardware key prompts (#53703)
* Propagate contextual key info from key store through to hardware key prompts. * Remove HardwareKeyPromptConstructor. * Cleanup; add tests.
This commit is contained in:
@@ -58,7 +58,7 @@ func NewCLIPrompt(w io.Writer, r prompt.StdinReader) *cliPrompt {
|
||||
|
||||
// AskPIN prompts the user for a PIN. If the requirement is [PINOptional],
|
||||
// the prompt will offer the default PIN as a default value.
|
||||
func (c *cliPrompt) AskPIN(ctx context.Context, requirement PINPromptRequirement) (string, error) {
|
||||
func (c *cliPrompt) AskPIN(ctx context.Context, requirement PINPromptRequirement, _ ContextualKeyInfo) (string, error) {
|
||||
message := "Enter your YubiKey PIV PIN"
|
||||
if requirement == PINOptional {
|
||||
message = "Enter your YubiKey PIV PIN [blank to use default PIN]"
|
||||
@@ -68,7 +68,7 @@ func (c *cliPrompt) AskPIN(ctx context.Context, requirement PINPromptRequirement
|
||||
}
|
||||
|
||||
// Touch prompts the user to touch the hardware key.
|
||||
func (c *cliPrompt) Touch(_ context.Context) error {
|
||||
func (c *cliPrompt) Touch(_ context.Context, _ ContextualKeyInfo) error {
|
||||
_, err := fmt.Fprintln(c.writer, "Tap your YubiKey")
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
@@ -77,7 +77,7 @@ func (c *cliPrompt) Touch(_ context.Context) error {
|
||||
// If the provided PUK is the default value, it will ask for a new PUK as well.
|
||||
// If an invalid PIN or PUK is provided, the user will be re-prompted until a
|
||||
// valid value is provided.
|
||||
func (c *cliPrompt) ChangePIN(ctx context.Context) (*PINAndPUK, error) {
|
||||
func (c *cliPrompt) ChangePIN(ctx context.Context, _ ContextualKeyInfo) (*PINAndPUK, error) {
|
||||
var pinAndPUK = &PINAndPUK{}
|
||||
for {
|
||||
fmt.Fprintf(c.writer, "Please set a new 6-8 character PIN.\n")
|
||||
@@ -155,7 +155,7 @@ func (c *cliPrompt) ChangePIN(ctx context.Context) (*PINAndPUK, error) {
|
||||
}
|
||||
|
||||
// ConfirmSlotOverwrite asks the user if the slot's private key and certificate can be overridden.
|
||||
func (c *cliPrompt) ConfirmSlotOverwrite(ctx context.Context, message string) (bool, error) {
|
||||
func (c *cliPrompt) ConfirmSlotOverwrite(ctx context.Context, message string, _ ContextualKeyInfo) (bool, error) {
|
||||
confirmation, err := prompt.Confirmation(ctx, c.writer, c.reader, message)
|
||||
return confirmation, trace.Wrap(err)
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ func TestChangePIN(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
PINAndPUK, err := prompt.ChangePIN(ctx)
|
||||
PINAndPUK, err := prompt.ChangePIN(ctx, hardwarekey.ContextualKeyInfo{})
|
||||
require.ErrorIs(t, err, tc.expectError)
|
||||
require.Equal(t, tc.expectPINAndPUK, PINAndPUK)
|
||||
})
|
||||
|
||||
@@ -33,7 +33,7 @@ type Service interface {
|
||||
NewPrivateKey(ctx context.Context, config PrivateKeyConfig) (*Signer, error)
|
||||
// Sign performs a cryptographic signature using the specified hardware
|
||||
// private key and provided signature parameters.
|
||||
Sign(ctx context.Context, ref *PrivateKeyRef, rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error)
|
||||
Sign(ctx context.Context, ref *PrivateKeyRef, keyInfo ContextualKeyInfo, rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error)
|
||||
// TODO(Joerger): DELETE IN v19.0.0
|
||||
// GetFullKeyRef gets the full [PrivateKeyRef] for an existing hardware private
|
||||
// key in the given slot of the hardware key with the given serial number.
|
||||
@@ -44,13 +44,17 @@ type Service interface {
|
||||
type Signer struct {
|
||||
service Service
|
||||
Ref *PrivateKeyRef
|
||||
KeyInfo ContextualKeyInfo
|
||||
}
|
||||
|
||||
// NewSigner returns a [Signer] for the given service and ref.
|
||||
func NewSigner(s Service, ref *PrivateKeyRef) *Signer {
|
||||
// keyInfo is an optional argument to supply additional contextual info
|
||||
// used to add additional context to prompts, e.g. ProxyHost.
|
||||
func NewSigner(s Service, ref *PrivateKeyRef, keyInfo ContextualKeyInfo) *Signer {
|
||||
return &Signer{
|
||||
service: s,
|
||||
Ref: ref,
|
||||
KeyInfo: keyInfo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +65,7 @@ func (h *Signer) Public() crypto.PublicKey {
|
||||
|
||||
// Sign implements [crypto.Signer].
|
||||
func (h *Signer) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||
return h.service.Sign(context.TODO(), h.Ref, rand, digest, opts)
|
||||
return h.service.Sign(context.TODO(), h.Ref, h.KeyInfo, rand, digest, opts)
|
||||
}
|
||||
|
||||
// GetAttestation returns the hardware private key attestation details.
|
||||
@@ -88,7 +92,7 @@ func (h *Signer) WarmupHardwareKey(ctx context.Context) error {
|
||||
// We don't actually need to hash the digest, just make it match the hash size.
|
||||
digest := make([]byte, hash.Size())
|
||||
|
||||
_, err := h.service.Sign(ctx, h.Ref, rand.Reader, digest, hash)
|
||||
_, err := h.service.Sign(ctx, h.Ref, h.KeyInfo, rand.Reader, digest, hash)
|
||||
return trace.Wrap(err, "failed to perform warmup signature with hardware private key")
|
||||
}
|
||||
|
||||
@@ -98,13 +102,13 @@ func EncodeSigner(p *Signer) ([]byte, error) {
|
||||
}
|
||||
|
||||
// DecodeSigner decodes an encoded hardware key signer for the given service.
|
||||
func DecodeSigner(encodedKey []byte, s Service) (*Signer, error) {
|
||||
func DecodeSigner(encodedKey []byte, s Service, keyInfo ContextualKeyInfo) (*Signer, error) {
|
||||
ref, err := decodeKeyRef(encodedKey, s)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return NewSigner(s, ref), nil
|
||||
return NewSigner(s, ref, keyInfo), nil
|
||||
}
|
||||
|
||||
// PrivateKeyRef references a specific hardware private key.
|
||||
@@ -230,4 +234,16 @@ type PrivateKeyConfig struct {
|
||||
// - touch & pin -> 9d
|
||||
// - touch & !pin -> 9e
|
||||
CustomSlot PIVSlotKeyString
|
||||
// ContextualKeyInfo contains additional info to associate with the key.
|
||||
ContextualKeyInfo ContextualKeyInfo
|
||||
}
|
||||
|
||||
// ContextualKeyInfo contains contextual information associated with a hardware [PrivateKey].
|
||||
type ContextualKeyInfo struct {
|
||||
// ProxyHost is the root proxy hostname that the key is associated with.
|
||||
ProxyHost string
|
||||
// Username is a Teleport username that the key is associated with.
|
||||
Username string
|
||||
// ClusterName is a Teleport cluster name that the key is associated with.
|
||||
ClusterName string
|
||||
}
|
||||
|
||||
@@ -42,16 +42,23 @@ func TestPrivateKey_EncodeDecode(t *testing.T) {
|
||||
|
||||
ctx := context.Background()
|
||||
s := hardwarekey.NewMockHardwareKeyService(nil /*prompt*/)
|
||||
|
||||
contextualKeyInfo := hardwarekey.ContextualKeyInfo{
|
||||
ProxyHost: "billy.io",
|
||||
Username: "Billy@billy.io",
|
||||
ClusterName: "billy.io",
|
||||
}
|
||||
|
||||
hwSigner, err := s.NewPrivateKey(ctx, hardwarekey.PrivateKeyConfig{
|
||||
Policy: hardwarekey.PromptPolicyNone,
|
||||
Policy: hardwarekey.PromptPolicyNone,
|
||||
ContextualKeyInfo: contextualKeyInfo,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
priv := hardwarekey.NewSigner(s, hwSigner.Ref)
|
||||
encoded, err := hardwarekey.EncodeSigner(priv)
|
||||
encoded, err := hardwarekey.EncodeSigner(hwSigner)
|
||||
require.NoError(t, err)
|
||||
|
||||
decodedSigner, err := hardwarekey.DecodeSigner(encoded, s)
|
||||
decodedSigner, err := hardwarekey.DecodeSigner(encoded, s, contextualKeyInfo)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, hwSigner, decodedSigner)
|
||||
}
|
||||
@@ -74,7 +81,7 @@ func TestPrivateKey_DecodePartialKeyRef(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
decodedSigner, err := hardwarekey.DecodeSigner(partialKeyRefJSON, s)
|
||||
decodedSigner, err := hardwarekey.DecodeSigner(partialKeyRefJSON, s, hardwarekey.ContextualKeyInfo{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, hwSigner, decodedSigner)
|
||||
}
|
||||
|
||||
@@ -43,17 +43,17 @@ type PromptPolicy struct {
|
||||
type Prompt interface {
|
||||
// AskPIN prompts the user for a PIN.
|
||||
// The requirement tells if the PIN is required or optional.
|
||||
AskPIN(ctx context.Context, requirement PINPromptRequirement) (string, error)
|
||||
AskPIN(ctx context.Context, requirement PINPromptRequirement, keyInfo ContextualKeyInfo) (string, error)
|
||||
// Touch prompts the user to touch the hardware key.
|
||||
Touch(ctx context.Context) error
|
||||
Touch(ctx context.Context, keyInfo ContextualKeyInfo) error
|
||||
// ChangePIN asks for a new PIN.
|
||||
// If the PUK has a default value, it should ask for the new value for it.
|
||||
// It is up to the implementer how the validation is handled.
|
||||
// For example, CLI prompt can ask for a valid PIN/PUK in a loop, a GUI
|
||||
// prompt can use the frontend validation.
|
||||
ChangePIN(ctx context.Context) (*PINAndPUK, error)
|
||||
ChangePIN(ctx context.Context, keyInfo ContextualKeyInfo) (*PINAndPUK, error)
|
||||
// ConfirmSlotOverwrite asks the user if the slot's private key and certificate can be overridden.
|
||||
ConfirmSlotOverwrite(ctx context.Context, message string) (bool, error)
|
||||
ConfirmSlotOverwrite(ctx context.Context, message string, keyInfo ContextualKeyInfo) (bool, error)
|
||||
}
|
||||
|
||||
// PINPromptRequirement specifies whether a PIN is required.
|
||||
|
||||
@@ -82,11 +82,11 @@ func (s *MockHardwareKeyService) NewPrivateKey(ctx context.Context, config Priva
|
||||
}
|
||||
|
||||
if priv, ok := s.fakeHardwarePrivateKeys[keySlot]; ok {
|
||||
return NewSigner(s, priv.ref), nil
|
||||
return NewSigner(s, priv.ref, config.ContextualKeyInfo), nil
|
||||
}
|
||||
|
||||
// generating a new key with PIN/touch requirements requires the corresponding prompt.
|
||||
if err := s.tryPrompt(ctx, config.Policy); err != nil {
|
||||
if err := s.tryPrompt(ctx, config.Policy, config.ContextualKeyInfo); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
@@ -115,12 +115,12 @@ func (s *MockHardwareKeyService) NewPrivateKey(ctx context.Context, config Priva
|
||||
ref: ref,
|
||||
}
|
||||
|
||||
return NewSigner(s, ref), nil
|
||||
return NewSigner(s, ref, config.ContextualKeyInfo), nil
|
||||
}
|
||||
|
||||
// Sign performs a cryptographic signature using the specified hardware
|
||||
// private key and provided signature parameters.
|
||||
func (s *MockHardwareKeyService) Sign(ctx context.Context, ref *PrivateKeyRef, rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||
func (s *MockHardwareKeyService) Sign(ctx context.Context, ref *PrivateKeyRef, keyInfo ContextualKeyInfo, rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||
s.fakeHardwarePrivateKeysMux.Lock()
|
||||
defer s.fakeHardwarePrivateKeysMux.Unlock()
|
||||
|
||||
@@ -132,14 +132,14 @@ func (s *MockHardwareKeyService) Sign(ctx context.Context, ref *PrivateKeyRef, r
|
||||
return nil, trace.NotFound("key not found in slot %d", ref.SlotKey)
|
||||
}
|
||||
|
||||
if err := s.tryPrompt(ctx, ref.Policy); err != nil {
|
||||
if err := s.tryPrompt(ctx, ref.Policy, keyInfo); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return priv.Sign(rand, digest, opts)
|
||||
}
|
||||
|
||||
func (s *MockHardwareKeyService) tryPrompt(ctx context.Context, policy PromptPolicy) error {
|
||||
func (s *MockHardwareKeyService) tryPrompt(ctx context.Context, policy PromptPolicy, keyInfo ContextualKeyInfo) error {
|
||||
s.promptMu.Lock()
|
||||
defer s.promptMu.Unlock()
|
||||
|
||||
@@ -150,14 +150,14 @@ func (s *MockHardwareKeyService) tryPrompt(ctx context.Context, policy PromptPol
|
||||
if policy.PINRequired {
|
||||
ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||
defer cancel()
|
||||
if _, err := s.prompt.AskPIN(ctx, PINRequired); err != nil {
|
||||
if _, err := s.prompt.AskPIN(ctx, PINRequired, keyInfo); err != nil {
|
||||
return trace.Wrap(err, "failed to handle pin prompt")
|
||||
}
|
||||
// We don't actually check the PIN for the current tests, any input is sufficient to unblock the prompt.
|
||||
}
|
||||
|
||||
if policy.TouchRequired {
|
||||
if err := s.prompt.Touch(ctx); err != nil {
|
||||
if err := s.prompt.Touch(ctx, keyInfo); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
select {
|
||||
|
||||
@@ -101,7 +101,7 @@ func (s *YubiKeyService) NewPrivateKey(ctx context.Context, config hardwarekey.P
|
||||
|
||||
// If PIN is required, check that PIN and PUK are not the defaults.
|
||||
if config.Policy.PINRequired {
|
||||
if err := s.checkOrSetPIN(ctx, y); err != nil {
|
||||
if err := s.checkOrSetPIN(ctx, y, config.ContextualKeyInfo); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,7 @@ func (s *YubiKeyService) NewPrivateKey(ctx context.Context, config hardwarekey.P
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return hardwarekey.NewSigner(s, ref), nil
|
||||
return hardwarekey.NewSigner(s, ref, config.ContextualKeyInfo), nil
|
||||
}
|
||||
|
||||
// If a custom slot was not specified, check for a key in the
|
||||
@@ -127,7 +127,7 @@ func (s *YubiKeyService) NewPrivateKey(ctx context.Context, config hardwarekey.P
|
||||
// Unknown cert found, this slot could be in use by a non-teleport client.
|
||||
// Prompt the user before we overwrite the slot.
|
||||
case len(cert.Subject.Organization) == 0 || cert.Subject.Organization[0] != certOrgName:
|
||||
if err := s.promptOverwriteSlot(ctx, nonTeleportCertificateMessage(pivSlot, cert)); err != nil {
|
||||
if err := s.promptOverwriteSlot(ctx, nonTeleportCertificateMessage(pivSlot, cert), config.ContextualKeyInfo); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return generatePrivateKey()
|
||||
@@ -146,25 +146,25 @@ func (s *YubiKeyService) NewPrivateKey(ctx context.Context, config hardwarekey.P
|
||||
|
||||
case config.Policy.TouchRequired && !keyRef.Policy.TouchRequired:
|
||||
msg := fmt.Sprintf("private key in YubiKey PIV slot %q does not require touch.", pivSlot)
|
||||
if err := s.promptOverwriteSlot(ctx, msg); err != nil {
|
||||
if err := s.promptOverwriteSlot(ctx, msg, config.ContextualKeyInfo); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return generatePrivateKey()
|
||||
|
||||
case config.Policy.PINRequired && !keyRef.Policy.PINRequired:
|
||||
msg := fmt.Sprintf("private key in YubiKey PIV slot %q does not require PIN", pivSlot)
|
||||
if err := s.promptOverwriteSlot(ctx, msg); err != nil {
|
||||
if err := s.promptOverwriteSlot(ctx, msg, config.ContextualKeyInfo); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return generatePrivateKey()
|
||||
}
|
||||
|
||||
return hardwarekey.NewSigner(s, keyRef), nil
|
||||
return hardwarekey.NewSigner(s, keyRef, config.ContextualKeyInfo), nil
|
||||
}
|
||||
|
||||
// Sign performs a cryptographic signature using the specified hardware
|
||||
// private key and provided signature parameters.
|
||||
func (s *YubiKeyService) Sign(ctx context.Context, ref *hardwarekey.PrivateKeyRef, rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||
func (s *YubiKeyService) Sign(ctx context.Context, ref *hardwarekey.PrivateKeyRef, keyInfo hardwarekey.ContextualKeyInfo, rand io.Reader, digest []byte, opts crypto.SignerOpts) (signature []byte, err error) {
|
||||
y, err := s.getYubiKey(ref.SerialNumber)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
@@ -173,7 +173,7 @@ func (s *YubiKeyService) Sign(ctx context.Context, ref *hardwarekey.PrivateKeyRe
|
||||
promptMux.Lock()
|
||||
defer promptMux.Unlock()
|
||||
|
||||
return y.sign(ctx, ref, s.prompt, rand, digest, opts)
|
||||
return y.sign(ctx, ref, keyInfo, s.prompt, rand, digest, opts)
|
||||
}
|
||||
|
||||
// TODO(Joerger): Re-attesting the key every time we decode a hardware key signer is very resource
|
||||
@@ -246,11 +246,11 @@ func (s *YubiKeyService) getYubiKey(serialNumber uint32) (*YubiKey, error) {
|
||||
// checkOrSetPIN prompts the user for PIN and verifies it with the YubiKey.
|
||||
// If the user provides the default PIN, they will be prompted to set a
|
||||
// non-default PIN and PUK before continuing.
|
||||
func (s *YubiKeyService) checkOrSetPIN(ctx context.Context, y *YubiKey) error {
|
||||
func (s *YubiKeyService) checkOrSetPIN(ctx context.Context, y *YubiKey, keyInfo hardwarekey.ContextualKeyInfo) error {
|
||||
promptMux.Lock()
|
||||
defer promptMux.Unlock()
|
||||
|
||||
pin, err := s.prompt.AskPIN(ctx, hardwarekey.PINOptional)
|
||||
pin, err := s.prompt.AskPIN(ctx, hardwarekey.PINOptional, keyInfo)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
@@ -260,7 +260,7 @@ func (s *YubiKeyService) checkOrSetPIN(ctx context.Context, y *YubiKey) error {
|
||||
fmt.Fprintf(os.Stderr, "The default PIN %q is not supported.\n", piv.DefaultPIN)
|
||||
fallthrough
|
||||
case "":
|
||||
pin, err = y.setPINAndPUKFromDefault(ctx, s.prompt)
|
||||
pin, err = y.setPINAndPUKFromDefault(ctx, s.prompt, keyInfo)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
@@ -269,12 +269,12 @@ func (s *YubiKeyService) checkOrSetPIN(ctx context.Context, y *YubiKey) error {
|
||||
return trace.Wrap(y.verifyPIN(pin))
|
||||
}
|
||||
|
||||
func (s *YubiKeyService) promptOverwriteSlot(ctx context.Context, msg string) error {
|
||||
func (s *YubiKeyService) promptOverwriteSlot(ctx context.Context, msg string, keyInfo hardwarekey.ContextualKeyInfo) error {
|
||||
promptMux.Lock()
|
||||
defer promptMux.Unlock()
|
||||
|
||||
promptQuestion := fmt.Sprintf("%v\nWould you like to overwrite this slot's private key and certificate?", msg)
|
||||
if confirmed, confirmErr := s.prompt.ConfirmSlotOverwrite(ctx, promptQuestion); confirmErr != nil {
|
||||
if confirmed, confirmErr := s.prompt.ConfirmSlotOverwrite(ctx, promptQuestion, keyInfo); confirmErr != nil {
|
||||
return trace.Wrap(confirmErr)
|
||||
} else if !confirmed {
|
||||
return trace.Wrap(trace.CompareFailed(msg), "user declined to overwrite slot")
|
||||
|
||||
@@ -41,7 +41,7 @@ func (s *unavailableYubiKeyPIVService) NewPrivateKey(_ context.Context, _ hardwa
|
||||
|
||||
// Sign performs a cryptographic signature using the specified hardware
|
||||
// private key and provided signature parameters.
|
||||
func (s *unavailableYubiKeyPIVService) Sign(_ context.Context, _ *hardwarekey.PrivateKeyRef, _ io.Reader, _ []byte, _ crypto.SignerOpts) ([]byte, error) {
|
||||
func (s *unavailableYubiKeyPIVService) Sign(_ context.Context, _ *hardwarekey.PrivateKeyRef, _ hardwarekey.ContextualKeyInfo, _ io.Reader, _ []byte, _ crypto.SignerOpts) ([]byte, error) {
|
||||
return nil, trace.Wrap(errPIVUnavailable)
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ const (
|
||||
signTouchPromptDelay = time.Millisecond * 200
|
||||
)
|
||||
|
||||
func (y *YubiKey) sign(ctx context.Context, ref *hardwarekey.PrivateKeyRef, prompt hardwarekey.Prompt, rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||
func (y *YubiKey) sign(ctx context.Context, ref *hardwarekey.PrivateKeyRef, keyInfo hardwarekey.ContextualKeyInfo, prompt hardwarekey.Prompt, rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
|
||||
ctx, cancel := context.WithCancelCause(ctx)
|
||||
defer cancel(nil)
|
||||
|
||||
@@ -163,7 +163,7 @@ func (y *YubiKey) sign(ctx context.Context, ref *hardwarekey.PrivateKeyRef, prom
|
||||
select {
|
||||
case <-touchPromptDelayTimer.C:
|
||||
// Prompt for touch after a delay, in case the function succeeds without touch due to a cached touch.
|
||||
err := prompt.Touch(ctx)
|
||||
err := prompt.Touch(ctx, keyInfo)
|
||||
if err != nil {
|
||||
// Cancel the entire function when an error occurs.
|
||||
// This is typically used for aborting the prompt.
|
||||
@@ -185,7 +185,7 @@ func (y *YubiKey) sign(ctx context.Context, ref *hardwarekey.PrivateKeyRef, prom
|
||||
defer touchPromptDelayTimer.Reset(signTouchPromptDelay)
|
||||
}
|
||||
}
|
||||
pass, err := prompt.AskPIN(ctx, hardwarekey.PINRequired)
|
||||
pass, err := prompt.AskPIN(ctx, hardwarekey.PINRequired, keyInfo)
|
||||
return pass, trace.Wrap(err)
|
||||
}
|
||||
|
||||
@@ -399,8 +399,8 @@ func (y *YubiKey) SetPIN(oldPin, newPin string) error {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
func (y *YubiKey) setPINAndPUKFromDefault(ctx context.Context, prompt hardwarekey.Prompt) (string, error) {
|
||||
pinAndPUK, err := prompt.ChangePIN(ctx)
|
||||
func (y *YubiKey) setPINAndPUKFromDefault(ctx context.Context, prompt hardwarekey.Prompt, keyInfo hardwarekey.ContextualKeyInfo) (string, error) {
|
||||
pinAndPUK, err := prompt.ChangePIN(ctx, keyInfo)
|
||||
if err != nil {
|
||||
return "", trace.Wrap(err)
|
||||
}
|
||||
|
||||
@@ -253,6 +253,8 @@ func LoadPrivateKey(keyFile string) (*PrivateKey, error) {
|
||||
type ParsePrivateKeyOptions struct {
|
||||
// HardwareKeyService is the hardware key service to use with parsed hardware private keys.
|
||||
HardwareKeyService hardwarekey.Service
|
||||
// ContextualKeyInfo is contextual information associated with the key.
|
||||
ContextualKeyInfo hardwarekey.ContextualKeyInfo
|
||||
}
|
||||
|
||||
// ParsePrivateKeyOpt applies configuration options.
|
||||
@@ -265,6 +267,13 @@ func WithHardwareKeyService(hwKeyService hardwarekey.Service) ParsePrivateKeyOpt
|
||||
}
|
||||
}
|
||||
|
||||
// WithContextualKeyInfo adds contextual key info to the parsed private key.
|
||||
func WithContextualKeyInfo(info hardwarekey.ContextualKeyInfo) ParsePrivateKeyOpt {
|
||||
return func(o *ParsePrivateKeyOptions) {
|
||||
o.ContextualKeyInfo = info
|
||||
}
|
||||
}
|
||||
|
||||
// ParsePrivateKey returns the PrivateKey for the given key PEM block.
|
||||
// Allows passing a custom hardware key prompt.
|
||||
func ParsePrivateKey(keyPEM []byte, opts ...ParsePrivateKeyOpt) (*PrivateKey, error) {
|
||||
@@ -296,7 +305,7 @@ func ParsePrivateKey(keyPEM []byte, opts ...ParsePrivateKeyOpt) (*PrivateKey, er
|
||||
hwks = piv.NewYubiKeyService(nil /*prompt*/)
|
||||
}
|
||||
|
||||
hwSigner, err := hardwarekey.DecodeSigner(block.Bytes, hwks)
|
||||
hwSigner, err := hardwarekey.DecodeSigner(block.Bytes, hwks, appliedOpts.ContextualKeyInfo)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "failed to parse hardware key signer")
|
||||
}
|
||||
|
||||
@@ -49,8 +49,15 @@ func TestMarshalAndParseKey(t *testing.T) {
|
||||
_, edKey, err := ed25519.GenerateKey(rand.Reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
contextualKeyInfo := hardwarekey.ContextualKeyInfo{
|
||||
ProxyHost: "billy.io",
|
||||
Username: "Billy@billy.io",
|
||||
ClusterName: "billy.io",
|
||||
}
|
||||
s := hardwarekey.NewMockHardwareKeyService(nil /*prompt*/)
|
||||
hwPriv, err := s.NewPrivateKey(context.TODO(), hardwarekey.PrivateKeyConfig{})
|
||||
hwPriv, err := s.NewPrivateKey(context.TODO(), hardwarekey.PrivateKeyConfig{
|
||||
ContextualKeyInfo: contextualKeyInfo,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
for keyType, key := range map[string]crypto.Signer{
|
||||
@@ -62,7 +69,7 @@ func TestMarshalAndParseKey(t *testing.T) {
|
||||
t.Run(keyType, func(t *testing.T) {
|
||||
keyPEM, err := keys.MarshalPrivateKey(key)
|
||||
require.NoError(t, err)
|
||||
gotKey, err := keys.ParsePrivateKey(keyPEM, keys.WithHardwareKeyService(s))
|
||||
gotKey, err := keys.ParsePrivateKey(keyPEM, keys.WithHardwareKeyService(s), keys.WithContextualKeyInfo(contextualKeyInfo))
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, cmp.Diff(key, gotKey.Signer, cmpopts.IgnoreUnexported(hardwarekey.Signer{})), "parsed private key is not equal to the original")
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ import (
|
||||
"github.com/gravitational/teleport/api/client/proto"
|
||||
"github.com/gravitational/teleport/api/constants"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/api/utils/keys/hardwarekey"
|
||||
api "github.com/gravitational/teleport/gen/proto/go/teleport/lib/teleterm/v1"
|
||||
"github.com/gravitational/teleport/integration/appaccess"
|
||||
dbhelpers "github.com/gravitational/teleport/integration/db"
|
||||
@@ -248,9 +247,6 @@ func testGatewayCertRenewal(ctx context.Context, t *testing.T, params gatewayCer
|
||||
// db cert has expired.
|
||||
Clock: fakeClock,
|
||||
WebauthnLogin: webauthnLogin,
|
||||
HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -882,9 +878,6 @@ func testTeletermAppGatewayTargetPortValidation(t *testing.T, pack *appaccess.Pa
|
||||
storage, err := clusters.NewStorage(clusters.Config{
|
||||
Dir: tc.KeysDir,
|
||||
InsecureSkipVerify: tc.InsecureSkipVerify,
|
||||
HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
daemonService, err := daemon.New(daemon.Config{
|
||||
|
||||
@@ -43,7 +43,6 @@ import (
|
||||
"github.com/gravitational/teleport/api/defaults"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/api/utils"
|
||||
"github.com/gravitational/teleport/api/utils/keys/hardwarekey"
|
||||
api "github.com/gravitational/teleport/gen/proto/go/teleport/lib/teleterm/v1"
|
||||
dbhelpers "github.com/gravitational/teleport/integration/db"
|
||||
"github.com/gravitational/teleport/integration/helpers"
|
||||
@@ -258,9 +257,6 @@ func testAddingRootCluster(t *testing.T, pack *dbhelpers.DatabasePack, creds *he
|
||||
storage, err := clusters.NewStorage(clusters.Config{
|
||||
Dir: t.TempDir(),
|
||||
InsecureSkipVerify: true,
|
||||
HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -293,9 +289,6 @@ func testListRootClustersReturnsLoggedInUser(t *testing.T, pack *dbhelpers.Datab
|
||||
storage, err := clusters.NewStorage(clusters.Config{
|
||||
Dir: tc.KeysDir,
|
||||
InsecureSkipVerify: tc.InsecureSkipVerify,
|
||||
HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -378,9 +371,6 @@ func testGetClusterReturnsPropertiesFromAuthServer(t *testing.T, pack *dbhelpers
|
||||
storage, err := clusters.NewStorage(clusters.Config{
|
||||
Dir: tc.KeysDir,
|
||||
InsecureSkipVerify: tc.InsecureSkipVerify,
|
||||
HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -433,9 +423,6 @@ func testHeadlessWatcher(t *testing.T, pack *dbhelpers.DatabasePack, creds *help
|
||||
storage, err := clusters.NewStorage(clusters.Config{
|
||||
Dir: tc.KeysDir,
|
||||
InsecureSkipVerify: tc.InsecureSkipVerify,
|
||||
HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -504,9 +491,6 @@ func testClientCache(t *testing.T, pack *dbhelpers.DatabasePack, creds *helpers.
|
||||
Dir: tc.KeysDir,
|
||||
Clock: storageFakeClock,
|
||||
InsecureSkipVerify: tc.InsecureSkipVerify,
|
||||
HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -766,9 +750,6 @@ func testCreateConnectMyComputerRole(t *testing.T, pack *dbhelpers.DatabasePack)
|
||||
storage, err := clusters.NewStorage(clusters.Config{
|
||||
Dir: t.TempDir(),
|
||||
InsecureSkipVerify: true,
|
||||
HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -885,9 +866,6 @@ func testCreateConnectMyComputerToken(t *testing.T, pack *dbhelpers.DatabasePack
|
||||
InsecureSkipVerify: tc.InsecureSkipVerify,
|
||||
Clock: fakeClock,
|
||||
WebauthnLogin: webauthnLogin,
|
||||
HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -948,9 +926,6 @@ func testWaitForConnectMyComputerNodeJoin(t *testing.T, pack *dbhelpers.Database
|
||||
storage, err := clusters.NewStorage(clusters.Config{
|
||||
Dir: tc.KeysDir,
|
||||
InsecureSkipVerify: tc.InsecureSkipVerify,
|
||||
HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1035,9 +1010,6 @@ func testDeleteConnectMyComputerNode(t *testing.T, pack *dbhelpers.DatabasePack)
|
||||
storage, err := clusters.NewStorage(clusters.Config{
|
||||
Dir: tc.KeysDir,
|
||||
InsecureSkipVerify: tc.InsecureSkipVerify,
|
||||
HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1265,9 +1237,6 @@ func testListDatabaseUsers(t *testing.T, pack *dbhelpers.DatabasePack) {
|
||||
storage, err := clusters.NewStorage(clusters.Config{
|
||||
Dir: tc.KeysDir,
|
||||
InsecureSkipVerify: tc.InsecureSkipVerify,
|
||||
HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -4007,6 +4007,11 @@ func (tc *TeleportClient) GetNewLoginKeyRing(ctx context.Context) (keyRing *KeyR
|
||||
priv, err := tc.ClientStore.NewHardwarePrivateKey(ctx, hardwarekey.PrivateKeyConfig{
|
||||
Policy: tc.PrivateKeyPolicy.GetPromptPolicy(),
|
||||
CustomSlot: tc.PIVSlot,
|
||||
ContextualKeyInfo: hardwarekey.ContextualKeyInfo{
|
||||
ProxyHost: tc.WebProxyHost(),
|
||||
Username: tc.Username,
|
||||
ClusterName: tc.SiteName,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
|
||||
@@ -192,7 +192,10 @@ func TestClientStore(t *testing.T) {
|
||||
// create a test software and hardware key.
|
||||
idx := KeyRingIndex{"test.proxy.com", "test-user", "root"}
|
||||
softKeyRing := a.makeSignedKeyRing(t, idx, false)
|
||||
hwPriv, err := keys.NewHardwarePrivateKey(ctx, hwks, hardwarekey.PrivateKeyConfig{})
|
||||
keyInfo := idx.contextualKeyInfo()
|
||||
hwPriv, err := keys.NewHardwarePrivateKey(ctx, hwks, hardwarekey.PrivateKeyConfig{
|
||||
ContextualKeyInfo: keyInfo,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
hardKeyRing := NewKeyRing(hwPriv, hwPriv)
|
||||
hardKeyRing.KeyRingIndex = idx
|
||||
@@ -230,6 +233,22 @@ func TestClientStore(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assertEqualKeyRings(t, keyRing, retrievedKeyRing)
|
||||
|
||||
// Get the key, now without cluster name. It should retrieve the key without certs
|
||||
// and without a cluster name in the KeyRingIndex or ContextualKeyInfo (hardware keys).
|
||||
retrievedKeyRing, err = clientStore.GetKeyRing(KeyRingIndex{idx.ProxyHost, idx.Username, ""})
|
||||
require.NoError(t, err)
|
||||
expectKeyRing = keyRing.Copy()
|
||||
expectKeyRing.ClusterName = ""
|
||||
expectKeyRing.Cert = nil
|
||||
expectKeyRing.DBTLSCredentials = make(map[string]TLSCredential)
|
||||
if hwPriv, ok := expectKeyRing.TLSPrivateKey.Signer.(*hardwarekey.Signer); ok {
|
||||
hwPriv.KeyInfo.ClusterName = ""
|
||||
}
|
||||
if hwPriv, ok := expectKeyRing.SSHPrivateKey.Signer.(*hardwarekey.Signer); ok {
|
||||
hwPriv.KeyInfo.ClusterName = ""
|
||||
}
|
||||
assertEqualKeyRings(t, expectKeyRing, retrievedKeyRing)
|
||||
|
||||
var profileDir string
|
||||
if fs, ok := clientStore.KeyStore.(*FSKeyStore); ok {
|
||||
profileDir = fs.KeyDir
|
||||
|
||||
@@ -40,6 +40,7 @@ import (
|
||||
"github.com/gravitational/teleport/api/constants"
|
||||
apiutils "github.com/gravitational/teleport/api/utils"
|
||||
"github.com/gravitational/teleport/api/utils/keys"
|
||||
"github.com/gravitational/teleport/api/utils/keys/hardwarekey"
|
||||
"github.com/gravitational/teleport/api/utils/sshutils"
|
||||
"github.com/gravitational/teleport/lib/auth/authclient"
|
||||
"github.com/gravitational/teleport/lib/cryptosuites"
|
||||
@@ -90,6 +91,14 @@ func (idx KeyRingIndex) LogValue() slog.Value {
|
||||
)
|
||||
}
|
||||
|
||||
func (idx KeyRingIndex) contextualKeyInfo() hardwarekey.ContextualKeyInfo {
|
||||
return hardwarekey.ContextualKeyInfo{
|
||||
ProxyHost: idx.ProxyHost,
|
||||
Username: idx.Username,
|
||||
ClusterName: idx.ClusterName,
|
||||
}
|
||||
}
|
||||
|
||||
// TLSCredential holds a signed TLS certificate and matching private key.
|
||||
type TLSCredential struct {
|
||||
// PrivateKey is the private key of the credential.
|
||||
|
||||
@@ -540,7 +540,7 @@ func (fs *FSKeyStore) GetKeyRing(idx KeyRingIndex, hwks hardwarekey.Service, opt
|
||||
return nil, trace.Wrap(err, "no session keys for %+v", idx)
|
||||
}
|
||||
|
||||
tlsCred, err := readTLSCredential(fs.userTLSKeyPath(idx), fs.tlsCertPath(idx), keys.WithHardwareKeyService(hwks))
|
||||
tlsCred, err := readTLSCredential(fs.userTLSKeyPath(idx), fs.tlsCertPath(idx), keys.WithHardwareKeyService(hwks), keys.WithContextualKeyInfo(idx.contextualKeyInfo()))
|
||||
if err != nil {
|
||||
if trace.IsNotFound(err) {
|
||||
if _, statErr := os.Stat(fs.tlsCertPathLegacy(idx)); statErr == nil {
|
||||
@@ -551,7 +551,7 @@ func (fs *FSKeyStore) GetKeyRing(idx KeyRingIndex, hwks hardwarekey.Service, opt
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
sshPriv, err := keys.LoadKeyPair(fs.userSSHKeyPath(idx), fs.publicKeyPath(idx), keys.WithHardwareKeyService(hwks))
|
||||
sshPriv, err := keys.LoadKeyPair(fs.userSSHKeyPath(idx), fs.publicKeyPath(idx), keys.WithHardwareKeyService(hwks), keys.WithContextualKeyInfo(idx.contextualKeyInfo()))
|
||||
if err != nil {
|
||||
return nil, trace.ConvertSystemError(err)
|
||||
}
|
||||
@@ -574,7 +574,7 @@ func (fs *FSKeyStore) GetKeyRing(idx KeyRingIndex, hwks hardwarekey.Service, opt
|
||||
}
|
||||
|
||||
func (fs *FSKeyStore) updateKeyRingWithCerts(o CertOption, hwks hardwarekey.Service, keyRing *KeyRing) error {
|
||||
return o.updateKeyRing(fs.KeyDir, keyRing.KeyRingIndex, keyRing, keys.WithHardwareKeyService(hwks))
|
||||
return o.updateKeyRing(fs.KeyDir, keyRing.KeyRingIndex, keyRing, keys.WithHardwareKeyService(hwks), keys.WithContextualKeyInfo(keyRing.contextualKeyInfo()))
|
||||
}
|
||||
|
||||
// GetSSHCertificates gets all certificates signed for the given user and proxy.
|
||||
|
||||
@@ -61,7 +61,9 @@ func TestKeyStore(t *testing.T) {
|
||||
// create a test software and hardware key.
|
||||
idx := KeyRingIndex{"test.proxy.com", "test-user", "root"}
|
||||
softKeyRing := s.makeSignedKeyRing(t, idx, false)
|
||||
hwPriv, err := keys.NewHardwarePrivateKey(ctx, hwks, hardwarekey.PrivateKeyConfig{})
|
||||
hwPriv, err := keys.NewHardwarePrivateKey(ctx, hwks, hardwarekey.PrivateKeyConfig{
|
||||
ContextualKeyInfo: idx.contextualKeyInfo(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
hardKeyRing := NewKeyRing(hwPriv, hwPriv)
|
||||
hardKeyRing.KeyRingIndex = idx
|
||||
@@ -98,11 +100,18 @@ func TestKeyStore(t *testing.T) {
|
||||
expectKeyRing.DBTLSCredentials = make(map[string]TLSCredential)
|
||||
assertEqualKeyRings(t, expectKeyRing, retrievedKeyRing)
|
||||
|
||||
// check for the key, now without cluster name
|
||||
// Get the key, now without cluster name. It should retrieve the key without certs
|
||||
// and without a cluster name in the KeyRingIndex or ContextualKeyInfo (hardware keys).
|
||||
retrievedKeyRing, err = keyStore.GetKeyRing(KeyRingIndex{idx.ProxyHost, idx.Username, ""}, hwks)
|
||||
require.NoError(t, err)
|
||||
expectKeyRing.ClusterName = ""
|
||||
expectKeyRing.Cert = nil
|
||||
if hwPriv, ok := expectKeyRing.TLSPrivateKey.Signer.(*hardwarekey.Signer); ok {
|
||||
hwPriv.KeyInfo.ClusterName = ""
|
||||
}
|
||||
if hwPriv, ok := expectKeyRing.SSHPrivateKey.Signer.(*hardwarekey.Signer); ok {
|
||||
hwPriv.KeyInfo.ClusterName = ""
|
||||
}
|
||||
assertEqualKeyRings(t, expectKeyRing, retrievedKeyRing)
|
||||
|
||||
// delete the key
|
||||
|
||||
@@ -27,7 +27,6 @@ import (
|
||||
"github.com/gravitational/teleport"
|
||||
"github.com/gravitational/teleport/api/utils/keys/hardwarekey"
|
||||
"github.com/gravitational/teleport/lib/client"
|
||||
"github.com/gravitational/teleport/lib/teleterm/api/uri"
|
||||
)
|
||||
|
||||
// Config is the cluster service config
|
||||
@@ -47,7 +46,7 @@ type Config struct {
|
||||
AddKeysToAgent string
|
||||
// CustomHardwareKeyPrompt is a custom hardware key prompt to use when asking
|
||||
// for a hardware key PIN, touch, etc.
|
||||
HardwareKeyPromptConstructor func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt
|
||||
CustomHardwareKeyPrompt hardwarekey.Prompt
|
||||
}
|
||||
|
||||
// CheckAndSetDefaults checks the configuration for its validity and sets default values if needed
|
||||
@@ -56,10 +55,6 @@ func (c *Config) CheckAndSetDefaults() error {
|
||||
return trace.BadParameter("missing working directory")
|
||||
}
|
||||
|
||||
if c.HardwareKeyPromptConstructor == nil {
|
||||
return trace.BadParameter("missing hardware key prompt constructor")
|
||||
}
|
||||
|
||||
if c.Clock == nil {
|
||||
c.Clock = clockwork.NewRealClock()
|
||||
}
|
||||
|
||||
@@ -286,11 +286,7 @@ func (s *Storage) makeDefaultClientConfig(rootClusterURI uri.ResourceURI) *clien
|
||||
cfg.InsecureSkipVerify = s.InsecureSkipVerify
|
||||
cfg.AddKeysToAgent = s.AddKeysToAgent
|
||||
cfg.WebauthnLogin = s.WebauthnLogin
|
||||
// TODO(Joerger): Remove the rootClusterURI dependency from the teleterm prompt so that
|
||||
// the storage service can share a single hardware key service+prompt. This allows the
|
||||
// process to properly share PIV connections, prevents duplicate prompts, and enables
|
||||
// PIN caching across clusters (if both clusters allow PIN caching).
|
||||
cfg.CustomHardwareKeyPrompt = s.HardwareKeyPromptConstructor(rootClusterURI)
|
||||
cfg.CustomHardwareKeyPrompt = s.CustomHardwareKeyPrompt
|
||||
cfg.DTAuthnRunCeremony = dtauthn.NewCeremony().Run
|
||||
cfg.DTAutoEnroll = dtenroll.AutoEnroll
|
||||
return cfg
|
||||
|
||||
@@ -40,7 +40,6 @@ import (
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/api/utils/keys/hardwarekey"
|
||||
api "github.com/gravitational/teleport/gen/proto/go/teleport/lib/teleterm/v1"
|
||||
"github.com/gravitational/teleport/lib/client"
|
||||
"github.com/gravitational/teleport/lib/client/clientcache"
|
||||
@@ -348,9 +347,6 @@ func TestUpdateTshdEventsServerAddress(t *testing.T) {
|
||||
storage, err := clusters.NewStorage(clusters.Config{
|
||||
Dir: homeDir,
|
||||
InsecureSkipVerify: true,
|
||||
HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -385,9 +381,6 @@ func TestUpdateTshdEventsServerAddress_CredsErr(t *testing.T) {
|
||||
storage, err := clusters.NewStorage(clusters.Config{
|
||||
Dir: homeDir,
|
||||
InsecureSkipVerify: true,
|
||||
HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -489,9 +482,6 @@ func TestRetryWithRelogin(t *testing.T) {
|
||||
storage, err := clusters.NewStorage(clusters.Config{
|
||||
Dir: t.TempDir(),
|
||||
InsecureSkipVerify: true,
|
||||
HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -545,9 +535,6 @@ func TestConcurrentHeadlessAuthPrompts(t *testing.T) {
|
||||
storage, err := clusters.NewStorage(clusters.Config{
|
||||
Dir: t.TempDir(),
|
||||
InsecureSkipVerify: true,
|
||||
HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -25,11 +25,9 @@ import (
|
||||
|
||||
"github.com/gravitational/teleport/api/utils/keys/hardwarekey"
|
||||
api "github.com/gravitational/teleport/gen/proto/go/teleport/lib/teleterm/v1"
|
||||
"github.com/gravitational/teleport/lib/teleterm/api/uri"
|
||||
)
|
||||
|
||||
// NewHardwareKeyPromptConstructor returns a new hardware key prompt constructor
|
||||
// for this service and the given root cluster URI.
|
||||
// NewHardwareKeyPrompt returns a new hardware key prompt.
|
||||
//
|
||||
// TODO(gzdunek): Improve multi-cluster and multi-hardware keys support.
|
||||
// The code in yubikey.go doesn't really support using multiple hardware keys (like one per cluster):
|
||||
@@ -46,19 +44,18 @@ import (
|
||||
// Because the code in yubikey.go assumes you use a single key, we don't have any mutex here.
|
||||
// (unlike other modals triggered by tshd).
|
||||
// We don't expect receiving prompts from different hardware keys.
|
||||
func (s *Service) NewHardwareKeyPromptConstructor(rootClusterURI uri.ResourceURI) hardwarekey.Prompt {
|
||||
return &hardwareKeyPrompter{s: s, rootClusterURI: rootClusterURI}
|
||||
func (s *Service) NewHardwareKeyPrompt() hardwarekey.Prompt {
|
||||
return &hardwareKeyPrompter{s: s}
|
||||
}
|
||||
|
||||
type hardwareKeyPrompter struct {
|
||||
s *Service
|
||||
rootClusterURI uri.ResourceURI
|
||||
s *Service
|
||||
}
|
||||
|
||||
// Touch prompts the user to touch the hardware key.
|
||||
func (h *hardwareKeyPrompter) Touch(ctx context.Context) error {
|
||||
func (h *hardwareKeyPrompter) Touch(ctx context.Context, keyInfo hardwarekey.ContextualKeyInfo) error {
|
||||
_, err := h.s.tshdEventsClient.PromptHardwareKeyTouch(ctx, &api.PromptHardwareKeyTouchRequest{
|
||||
RootClusterUri: h.rootClusterURI.String(),
|
||||
RootClusterUri: keyInfo.ProxyHost,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
@@ -67,9 +64,9 @@ func (h *hardwareKeyPrompter) Touch(ctx context.Context) error {
|
||||
}
|
||||
|
||||
// AskPIN prompts the user for a PIN.
|
||||
func (h *hardwareKeyPrompter) AskPIN(ctx context.Context, requirement hardwarekey.PINPromptRequirement) (string, error) {
|
||||
func (h *hardwareKeyPrompter) AskPIN(ctx context.Context, requirement hardwarekey.PINPromptRequirement, keyInfo hardwarekey.ContextualKeyInfo) (string, error) {
|
||||
res, err := h.s.tshdEventsClient.PromptHardwareKeyPIN(ctx, &api.PromptHardwareKeyPINRequest{
|
||||
RootClusterUri: h.rootClusterURI.String(),
|
||||
RootClusterUri: keyInfo.ProxyHost,
|
||||
PinOptional: requirement == hardwarekey.PINOptional,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -81,9 +78,9 @@ func (h *hardwareKeyPrompter) AskPIN(ctx context.Context, requirement hardwareke
|
||||
// ChangePIN asks for a new PIN.
|
||||
// The Electron app prompt must handle default values for PIN and PUK,
|
||||
// preventing the user from submitting empty/default values.
|
||||
func (h *hardwareKeyPrompter) ChangePIN(ctx context.Context) (*hardwarekey.PINAndPUK, error) {
|
||||
func (h *hardwareKeyPrompter) ChangePIN(ctx context.Context, keyInfo hardwarekey.ContextualKeyInfo) (*hardwarekey.PINAndPUK, error) {
|
||||
res, err := h.s.tshdEventsClient.PromptHardwareKeyPINChange(ctx, &api.PromptHardwareKeyPINChangeRequest{
|
||||
RootClusterUri: h.rootClusterURI.String(),
|
||||
RootClusterUri: keyInfo.ProxyHost,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
@@ -96,9 +93,9 @@ func (h *hardwareKeyPrompter) ChangePIN(ctx context.Context) (*hardwarekey.PINAn
|
||||
}
|
||||
|
||||
// ConfirmSlotOverwrite asks the user if the slot's private key and certificate can be overridden.
|
||||
func (h *hardwareKeyPrompter) ConfirmSlotOverwrite(ctx context.Context, message string) (bool, error) {
|
||||
func (h *hardwareKeyPrompter) ConfirmSlotOverwrite(ctx context.Context, message string, keyInfo hardwarekey.ContextualKeyInfo) (bool, error) {
|
||||
res, err := h.s.tshdEventsClient.ConfirmHardwareKeySlotOverwrite(ctx, &api.ConfirmHardwareKeySlotOverwriteRequest{
|
||||
RootClusterUri: h.rootClusterURI.String(),
|
||||
RootClusterUri: keyInfo.ProxyHost,
|
||||
Message: message,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -32,8 +32,6 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
|
||||
"github.com/gravitational/teleport/api/utils/keys/hardwarekey"
|
||||
"github.com/gravitational/teleport/lib/teleterm/api/uri"
|
||||
"github.com/gravitational/teleport/lib/teleterm/apiserver"
|
||||
"github.com/gravitational/teleport/lib/teleterm/clusteridcache"
|
||||
"github.com/gravitational/teleport/lib/teleterm/clusters"
|
||||
@@ -42,7 +40,6 @@ import (
|
||||
|
||||
// Serve starts daemon service
|
||||
func Serve(ctx context.Context, cfg Config) error {
|
||||
var hardwareKeyPromptConstructor func(clusterURI uri.ResourceURI) hardwarekey.Prompt
|
||||
if err := cfg.CheckAndSetDefaults(); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
@@ -59,9 +56,6 @@ func Serve(ctx context.Context, cfg Config) error {
|
||||
Clock: clock,
|
||||
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
||||
AddKeysToAgent: cfg.AddKeysToAgent,
|
||||
HardwareKeyPromptConstructor: func(rootClusterURI uri.ResourceURI) hardwarekey.Prompt {
|
||||
return hardwareKeyPromptConstructor(rootClusterURI)
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
@@ -83,7 +77,8 @@ func Serve(ctx context.Context, cfg Config) error {
|
||||
|
||||
// TODO(gzdunek): Move tshdEventsClient out of daemonService so that we can
|
||||
// construct the prompt before creating Storage.
|
||||
hardwareKeyPromptConstructor = daemonService.NewHardwareKeyPromptConstructor
|
||||
storage.CustomHardwareKeyPrompt = daemonService.NewHardwareKeyPrompt()
|
||||
|
||||
apiServer, err := apiserver.New(apiserver.Config{
|
||||
HostAddr: cfg.Addr,
|
||||
InsecureSkipVerify: cfg.InsecureSkipVerify,
|
||||
|
||||
Reference in New Issue
Block a user