Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
fe50953
Add clientside elements of TPM joining
strideynet Apr 18, 2024
b4aaba6
Update lib/auth/register.go
strideynet Apr 18, 2024
9e053c2
Update api/client/joinservice.go
strideynet Apr 18, 2024
1413642
Update lib/auth/register.go
strideynet Apr 18, 2024
270194e
Tidy up RegisterUsingTPMMethod method
strideynet Apr 18, 2024
85e4a9b
Add default case
strideynet Apr 18, 2024
a6d0b12
Rename CheckAndSetDefaults to validate
strideynet Apr 18, 2024
c1e56ea
Add basic success test for JoinServiceClient_RegisterUsingTPMMethod
strideynet Apr 19, 2024
49cda07
Add final touches to client joinservice test
strideynet Apr 22, 2024
a9b2800
Add license header to joinservice_test.go
strideynet Apr 23, 2024
4f90627
Add server-side elements of TPM joining
strideynet Apr 18, 2024
3a4ca11
Turn SAN extension code into helper func
strideynet Apr 19, 2024
2708d34
Add `ok` check to provision token casting
strideynet Apr 19, 2024
fd0f0fd
Improve test name
strideynet Apr 19, 2024
fa705ae
Update lib/auth/join_tpm.go
strideynet Apr 19, 2024
1b0fddf
Update lib/auth/join_tpm.go
strideynet Apr 19, 2024
8f77439
Unexported registerUsingTPMMethod
strideynet Apr 19, 2024
0ccd21c
Refactor enterprise error
strideynet Apr 19, 2024
e0a5d2e
tidy up test
strideynet Apr 19, 2024
2b564cb
Update lib/tpm/validate.go
strideynet Apr 22, 2024
f6e55d6
Update lib/auth/join_tpm.go
strideynet Apr 22, 2024
3513f07
Update lib/auth/join_tpm_test.go
strideynet Apr 22, 2024
6033be4
Fix StripSANExtensionOIDs and add test
strideynet Apr 22, 2024
77d3fa1
Improve joinserver.go, use simpler proto getter methods and use slog
strideynet Apr 22, 2024
1690b31
Tidy up join_tpm_test.go
strideynet Apr 22, 2024
a09db4e
Tidy up joinserver_test
strideynet Apr 22, 2024
a584117
Add join failure audit event
strideynet Apr 23, 2024
e78f211
Merge branch 'branch/v15' into bot/backport-40512-branch/v15
strideynet Apr 23, 2024
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
11 changes: 11 additions & 0 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"errors"
"fmt"
"io"
"log/slog"
"math"
"math/big"
insecurerand "math/rand"
Expand Down Expand Up @@ -111,6 +112,7 @@ import (
"github.com/gravitational/teleport/lib/sshca"
"github.com/gravitational/teleport/lib/sshutils"
"github.com/gravitational/teleport/lib/tlsca"
"github.com/gravitational/teleport/lib/tpm"
usagereporter "github.com/gravitational/teleport/lib/usagereporter/teleport"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/utils/interval"
Expand Down Expand Up @@ -495,6 +497,9 @@ func NewServer(cfg *InitConfig, opts ...ServerOption) (*Server, error) {
)
}
}
if as.tpmValidator == nil {
as.tpmValidator = tpm.Validate
}
if as.k8sTokenReviewValidator == nil {
as.k8sTokenReviewValidator = &kubernetestoken.TokenReviewValidator{}
}
Expand Down Expand Up @@ -880,6 +885,12 @@ type Server struct {
// the auth server. It can be overridden for the purpose of tests.
gitlabIDTokenValidator gitlabIDTokenValidator

// tpmValidator allows TPMs to be validated by the auth server. It can be
// overridden for the purpose of tests.
tpmValidator func(
ctx context.Context, log *slog.Logger, params tpm.ValidateParams,
) (*tpm.ValidatedTPM, error)

// circleCITokenValidate allows ID tokens from CircleCI to be validated by
// the auth server. It can be overridden for the purpose of tests.
circleCITokenValidate func(ctx context.Context, organizationID, token string) (*circleci.IDTokenClaims, error)
Expand Down
31 changes: 27 additions & 4 deletions lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,24 @@ func (a *ServerWithRoles) RegisterUsingAzureMethod(ctx context.Context, challeng
return certs, trace.Wrap(err)
}

// RegisterUsingTPMMethod registers the caller using the TPM join method and
// returns signed certs to join the cluster.
//
// See (*Server).RegisterUsingTPMMethod for further documentation.
//
// This wrapper does not do any extra authz checks, as the register method has
// its own authz mechanism.
func (a *ServerWithRoles) RegisterUsingTPMMethod(
ctx context.Context,
initReq *proto.RegisterUsingTPMMethodInitialRequest,
solveChallenge client.RegisterTPMChallengeResponseFunc,
) (*proto.Certs, error) {
certs, err := a.authServer.registerUsingTPMMethod(
ctx, initReq, solveChallenge,
)
return certs, trace.Wrap(err)
}

// GenerateHostCerts generates new host certificates (signed
// by the host certificate authority) for a node.
func (a *ServerWithRoles) GenerateHostCerts(ctx context.Context, req *proto.HostCertsRequest) (*proto.Certs, error) {
Expand Down Expand Up @@ -2027,15 +2045,20 @@ func enforceEnterpriseJoinMethodCreation(token types.ProvisionToken) error {
switch v.Spec.JoinMethod {
case types.JoinMethodGitHub:
if v.Spec.GitHub != nil && v.Spec.GitHub.EnterpriseServerHost != "" {
return fmt.Errorf(
"github enterprise server joining: %w",
return trace.Wrap(
ErrRequiresEnterprise,
"github enterprise server joining",
)
}
case types.JoinMethodSpacelift:
return fmt.Errorf(
"spacelift joining: %w",
return trace.Wrap(
ErrRequiresEnterprise,
"spacelift joining",
)
case types.JoinMethodTPM:
return trace.Wrap(
ErrRequiresEnterprise,
"tpm joining",
)
}

Expand Down
2 changes: 1 addition & 1 deletion lib/auth/join.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ func (a *Server) RegisterUsingToken(ctx context.Context, req *types.RegisterUsin
if err := a.checkEC2JoinRequest(ctx, req); err != nil {
return nil, trace.Wrap(err)
}
case types.JoinMethodIAM, types.JoinMethodAzure:
case types.JoinMethodIAM, types.JoinMethodAzure, types.JoinMethodTPM:
// IAM and Azure join methods must use gRPC register methods
return nil, trace.AccessDenied("this token is only valid for the %s "+
"join method but the node has connected to the wrong endpoint, make "+
Expand Down
137 changes: 137 additions & 0 deletions lib/auth/join_tpm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* Teleport
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package auth

import (
"context"
"crypto/x509"
"log/slog"

"github.com/google/go-attestation/attest"
"github.com/gravitational/trace"

"github.com/gravitational/teleport/api/client"
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/modules"
"github.com/gravitational/teleport/lib/tpm"
)

func (a *Server) registerUsingTPMMethod(
ctx context.Context,
initReq *proto.RegisterUsingTPMMethodInitialRequest,
solveChallenge client.RegisterTPMChallengeResponseFunc,
) (_ *proto.Certs, err error) {
var provisionToken types.ProvisionToken
var attributeSrc joinAttributeSourcer
defer func() {
// Emit a log message and audit event on join failure.
if err != nil {
a.handleJoinFailure(
err, provisionToken, attributeSrc, initReq.JoinRequest,
)
}
}()

// First, check the specified token exists, and is a TPM-type join token.
if err := initReq.JoinRequest.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
}
provisionToken, err = a.checkTokenJoinRequestCommon(ctx, initReq.JoinRequest)
if err != nil {
return nil, trace.Wrap(err)
}
ptv2, ok := provisionToken.(*types.ProvisionTokenV2)
if !ok {
return nil, trace.BadParameter("expected *types.ProvisionTokenV2, got %T", provisionToken)
}
if ptv2.Spec.JoinMethod != types.JoinMethodTPM {
return nil, trace.BadParameter("specified join token is not for `tpm` method")
}

if modules.GetModules().BuildType() != modules.BuildEnterprise {
return nil, trace.Wrap(
ErrRequiresEnterprise,
"tpm joining",
)
}

// Convert configured CAs to a CAPool
var certPool *x509.CertPool
if len(ptv2.Spec.TPM.EKCertAllowedCAs) > 0 {
certPool = x509.NewCertPool()
for i, ca := range ptv2.Spec.TPM.EKCertAllowedCAs {
if ok := certPool.AppendCertsFromPEM([]byte(ca)); !ok {
return nil, trace.BadParameter(
"ekcert_allowed_cas[%d] has an invalid or malformed PEM", i,
)
}
}
}

// TODO(noah): Use logger from TeleportProcess.
validatedEK, err := a.tpmValidator(ctx, slog.Default(), tpm.ValidateParams{
EKCert: initReq.GetEkCert(),
EKKey: initReq.GetEkKey(),
AttestParams: tpm.AttestationParametersFromProto(initReq.AttestationParams),
AllowedCAs: certPool,
Solve: func(ec *attest.EncryptedCredential) ([]byte, error) {
solution, err := solveChallenge(tpm.EncryptedCredentialToProto(ec))
if err != nil {
return nil, trace.Wrap(err)
}
return solution.Solution, nil
},
})
if err != nil {
return nil, trace.Wrap(err, "validating TPM EK")
}
attributeSrc = validatedEK

if err := checkTPMAllowRules(validatedEK, ptv2.Spec.TPM.Allow); err != nil {
return nil, trace.Wrap(err)
}

if initReq.JoinRequest.Role == types.RoleBot {
certs, err := a.generateCertsBot(
ctx, ptv2, initReq.JoinRequest, validatedEK,
)
return certs, trace.Wrap(err, "generating certs for bot")
}
certs, err := a.generateCerts(
ctx, ptv2, initReq.JoinRequest, validatedEK,
)
return certs, trace.Wrap(err, "generating certs for host")
}

func checkTPMAllowRules(tpm *tpm.ValidatedTPM, rules []*types.ProvisionTokenSpecV2TPM_Rule) error {
// If a single rule passes, accept the TPM
for _, rule := range rules {
if rule.EKPublicHash != "" && tpm.EKPubHash != rule.EKPublicHash {
continue
}
if rule.EKCertificateSerial != "" && tpm.EKCertSerial != rule.EKCertificateSerial {
continue
}

// All rules met.
return nil
}
return trace.AccessDenied("validated tpm attributes did not match any allow rules")
}
Loading