Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 6 additions & 16 deletions api/types/provisioning.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,15 +441,11 @@ func (p *ProvisionTokenV2) CheckAndSetDefaults() error {
return trace.Wrap(err, "spec.azure_devops: failed validation")
}
case JoinMethodBoundKeypair:
providerCfg := p.Spec.BoundKeypair
if providerCfg == nil {
return trace.BadParameter(
"spec.bound_keypair: must be configured for the join method %q",
JoinMethodBoundKeypair,
)
if p.Spec.BoundKeypair == nil {
p.Spec.BoundKeypair = &ProvisionTokenSpecV2BoundKeypair{}
}

if err := providerCfg.checkAndSetDefaults(); err != nil {
if err := p.Spec.BoundKeypair.checkAndSetDefaults(); err != nil {
return trace.Wrap(err, "spec.bound_keypair: failed validation")
}
default:
Expand Down Expand Up @@ -1032,22 +1028,16 @@ func (a *ProvisionTokenSpecV2AzureDevops) checkAndSetDefaults() error {
}

func (a *ProvisionTokenSpecV2BoundKeypair) checkAndSetDefaults() error {
Comment thread
timothyb89 marked this conversation as resolved.
// Note: don't attempt to initialize onboarding - at least for now - as it
// has required keys. This behavior may be relaxed when we add
// server-generated joining secrets.
if a.Onboarding == nil {
return trace.BadParameter("spec.bound_keypair.onboarding is required")
}

if a.Onboarding.RegistrationSecret == "" && a.Onboarding.InitialPublicKey == "" {
return trace.BadParameter("at least one of [initial_join_secret, " +
"initial_public_key] is required in spec.bound_keypair.onboarding")
a.Onboarding = &ProvisionTokenSpecV2BoundKeypair_OnboardingSpec{}
}

if a.Recovery == nil {
a.Recovery = &ProvisionTokenSpecV2BoundKeypair_RecoverySpec{}
}

// Limit must be >= 1 for the token to be useful. If zero, assume it's unset
// and provide a sane default.
if a.Recovery.Limit == 0 {
a.Recovery.Limit = 1
}
Expand Down
4 changes: 3 additions & 1 deletion api/types/provisioning_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1408,6 +1408,8 @@ func TestProvisionTokenV2_CheckAndSetDefaults(t *testing.T) {
},
},
{
// note: missing onboarding config is allowed; we'll generate some
// fields at creation/upsert time.
desc: "bound keypair missing onboarding config",
token: &ProvisionTokenV2{
Metadata: Metadata{
Expand All @@ -1419,7 +1421,7 @@ func TestProvisionTokenV2_CheckAndSetDefaults(t *testing.T) {
BoundKeypair: &ProvisionTokenSpecV2BoundKeypair{},
},
},
wantErr: true,
wantErr: false,
},
}

Expand Down
65 changes: 43 additions & 22 deletions lib/auth/join/boundkeypair/boundkeypair.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,24 +183,26 @@ func (c *ClientState) SignerForPublicKey(authorizedKeysBytes []byte) (crypto.Sig
return nil, trace.Wrap(err)
}

// Check the active key first.
activePubKeyBytes, err := c.ToPublicKeyBytes()
if err != nil {
return nil, trace.Wrap(err)
}

equal, err := pubKeyEqual(desiredPubKey, activePubKeyBytes)
if err != nil {
return nil, trace.Wrap(err)
} else if equal {
// Parse a fresh copy of the key since this will escape the mutex and we
// can't be sure our local copy is thread safe.
key, err := keys.ParsePrivateKey(c.PrivateKeyBytes)
// Check the active key first, if available.
if c.PrivateKey != nil {
activePubKeyBytes, err := c.ToPublicKeyBytes()
if err != nil {
return nil, trace.Wrap(err)
}

return key.Signer, nil
equal, err := pubKeyEqual(desiredPubKey, activePubKeyBytes)
if err != nil {
return nil, trace.Wrap(err)
} else if equal {
// Parse a fresh copy of the key since this will escape the mutex and we
// can't be sure our local copy is thread safe.
key, err := keys.ParsePrivateKey(c.PrivateKeyBytes)
if err != nil {
return nil, trace.Wrap(err)
}

return key.Signer, nil
}
}

// Otherwise, search through the key history. If a keypair rotation was
Expand Down Expand Up @@ -268,14 +270,16 @@ func (c *ClientState) SetActiveKey(signer crypto.Signer) error {
c.mu.Lock()
defer c.mu.Unlock()

equal, err := pubKeyEqual(signer.Public(), c.PrivateKey.Public())
if err != nil {
return trace.Wrap(err)
}
if c.PrivateKey != nil {
equal, err := pubKeyEqual(signer.Public(), c.PrivateKey.Public())
if err != nil {
return trace.Wrap(err)
}

if equal {
// nothing to do; specified key is already the active key
return nil
if equal {
// nothing to do; specified key is already the active key
return nil
}
}

key, err := keys.NewPrivateKey(signer)
Expand Down Expand Up @@ -356,6 +360,13 @@ func LoadClientState(ctx context.Context, fs FS) (*ClientState, error) {
return nil, trace.Wrap(err, "reading private key")
}

// The private key may be empty if this is an initial join attempt using a
// configured registration secret. This is allowed, but callers should
// handle this via `NewEmptyClientState()`
if len(privateKeyBytes) == 0 {
return nil, trace.NotFound("no active private key found")
}

joinStateBytes, err := fs.Read(ctx, JoinStatePath)
if trace.IsNotFound(err) {
// Join state doesn't exist, this is allowed.
Expand Down Expand Up @@ -443,7 +454,8 @@ func (c *ClientState) Store(ctx context.Context) error {

// NewUnboundClientState creates a new client state that has not yet been bound,
// i.e. a new keypair that has not been registered with Auth, and no prior join
// state.
// state. Join attempts using registration secrets should instead use
// `NewEmptyClientState`, which does not immediately generate a keypair.
func NewUnboundClientState(ctx context.Context, fs FS, getSuite cryptosuites.GetSuiteFunc) (*ClientState, error) {
key, err := cryptosuites.GenerateKey(ctx, getSuite, cryptosuites.BoundKeypairJoining)
if err != nil {
Expand Down Expand Up @@ -483,3 +495,12 @@ func NewUnboundClientState(ctx context.Context, fs FS, getSuite cryptosuites.Get
KeyHistory: history,
}, nil
}

// NewEmptyClientState creates a new ClientState with no existing active private
// key or key history. This is only appropriate when a registration secret
// should be used.
func NewEmptyClientState(fs FS) *ClientState {
return &ClientState{
fs: fs,
}
}
Loading
Loading