mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-31 20:39:37 +08:00
* Add delay for the test when expired token is deleted in background * Use require.Eventually
492 lines
14 KiB
Go
492 lines
14 KiB
Go
/*
|
|
Copyright 2022 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 auth
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gravitational/trace"
|
|
"github.com/jonboulle/clockwork"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/crypto/ssh"
|
|
|
|
"github.com/gravitational/teleport/api/client/proto"
|
|
apidefaults "github.com/gravitational/teleport/api/defaults"
|
|
"github.com/gravitational/teleport/api/types"
|
|
apievents "github.com/gravitational/teleport/api/types/events"
|
|
"github.com/gravitational/teleport/api/types/wrappers"
|
|
"github.com/gravitational/teleport/api/utils/sshutils"
|
|
"github.com/gravitational/teleport/lib/auth/testauthority"
|
|
"github.com/gravitational/teleport/lib/defaults"
|
|
"github.com/gravitational/teleport/lib/events"
|
|
"github.com/gravitational/teleport/lib/tlsca"
|
|
"github.com/gravitational/teleport/lib/utils"
|
|
)
|
|
|
|
func TestAuth_RegisterUsingToken(t *testing.T) {
|
|
ctx := context.Background()
|
|
p, err := newTestPack(ctx, t.TempDir())
|
|
require.NoError(t, err)
|
|
a := p.a
|
|
|
|
// create a static token
|
|
staticToken := types.ProvisionTokenV1{
|
|
Roles: []types.SystemRole{types.RoleNode},
|
|
Token: "static_token",
|
|
}
|
|
staticTokens, err := types.NewStaticTokens(types.StaticTokensSpecV2{
|
|
StaticTokens: []types.ProvisionTokenV1{staticToken},
|
|
})
|
|
require.NoError(t, err)
|
|
err = p.a.SetStaticTokens(staticTokens)
|
|
require.NoError(t, err)
|
|
|
|
// create a dynamic token
|
|
dynamicToken := generateTestToken(
|
|
ctx,
|
|
t,
|
|
types.SystemRoles{types.RoleNode}, time.Now().Add(time.Minute*30),
|
|
a,
|
|
)
|
|
|
|
sshPrivateKey, sshPublicKey, err := testauthority.New().GenerateKeyPair()
|
|
require.NoError(t, err)
|
|
|
|
tlsPublicKey, err := PrivateKeyToPublicKeyTLS(sshPrivateKey)
|
|
require.NoError(t, err)
|
|
|
|
testcases := []struct {
|
|
desc string
|
|
req *types.RegisterUsingTokenRequest
|
|
certsAssertion func(*proto.Certs)
|
|
errorAssertion func(error) bool
|
|
clock clockwork.Clock
|
|
waitTokenDeleted bool // Expired tokens are deleted in background, might need slight delay in relevant test
|
|
}{
|
|
{
|
|
desc: "reject empty",
|
|
req: &types.RegisterUsingTokenRequest{},
|
|
errorAssertion: trace.IsBadParameter,
|
|
},
|
|
{
|
|
desc: "reject no token",
|
|
req: &types.RegisterUsingTokenRequest{
|
|
HostID: "localhost",
|
|
NodeName: "node-name",
|
|
Role: types.RoleNode,
|
|
PublicSSHKey: sshPublicKey,
|
|
PublicTLSKey: tlsPublicKey,
|
|
},
|
|
errorAssertion: trace.IsBadParameter,
|
|
},
|
|
{
|
|
desc: "reject no HostID",
|
|
req: &types.RegisterUsingTokenRequest{
|
|
Token: staticToken.Token,
|
|
NodeName: "node-name",
|
|
Role: types.RoleNode,
|
|
PublicSSHKey: sshPublicKey,
|
|
PublicTLSKey: tlsPublicKey,
|
|
},
|
|
errorAssertion: trace.IsBadParameter,
|
|
},
|
|
{
|
|
desc: "allow no NodeName",
|
|
req: &types.RegisterUsingTokenRequest{
|
|
Token: staticToken.Token,
|
|
HostID: "localhost",
|
|
Role: types.RoleNode,
|
|
PublicSSHKey: sshPublicKey,
|
|
PublicTLSKey: tlsPublicKey,
|
|
},
|
|
},
|
|
{
|
|
desc: "reject no SSH pub",
|
|
req: &types.RegisterUsingTokenRequest{
|
|
Token: staticToken.Token,
|
|
HostID: "localhost",
|
|
NodeName: "node-name",
|
|
Role: types.RoleNode,
|
|
PublicTLSKey: tlsPublicKey,
|
|
},
|
|
errorAssertion: trace.IsBadParameter,
|
|
},
|
|
{
|
|
desc: "reject no TLS pub",
|
|
req: &types.RegisterUsingTokenRequest{
|
|
Token: staticToken.Token,
|
|
HostID: "localhost",
|
|
NodeName: "node-name",
|
|
Role: types.RoleNode,
|
|
PublicSSHKey: sshPublicKey,
|
|
},
|
|
errorAssertion: trace.IsBadParameter,
|
|
},
|
|
{
|
|
desc: "reject bad token",
|
|
req: &types.RegisterUsingTokenRequest{
|
|
Token: "not a token",
|
|
HostID: "localhost",
|
|
NodeName: "node-name",
|
|
Role: types.RoleNode,
|
|
PublicSSHKey: sshPublicKey,
|
|
PublicTLSKey: tlsPublicKey,
|
|
},
|
|
errorAssertion: trace.IsAccessDenied,
|
|
},
|
|
{
|
|
desc: "allow static token",
|
|
req: &types.RegisterUsingTokenRequest{
|
|
Token: staticToken.Token,
|
|
HostID: "localhost",
|
|
NodeName: "node-name",
|
|
Role: types.RoleNode,
|
|
PublicSSHKey: sshPublicKey,
|
|
PublicTLSKey: tlsPublicKey,
|
|
},
|
|
},
|
|
{
|
|
desc: "reject wrong role static",
|
|
req: &types.RegisterUsingTokenRequest{
|
|
Token: staticToken.Token,
|
|
HostID: "localhost",
|
|
NodeName: "node-name",
|
|
Role: types.RoleProxy,
|
|
PublicSSHKey: sshPublicKey,
|
|
PublicTLSKey: tlsPublicKey,
|
|
},
|
|
errorAssertion: trace.IsBadParameter,
|
|
},
|
|
{
|
|
desc: "allow dynamic token",
|
|
req: &types.RegisterUsingTokenRequest{
|
|
Token: dynamicToken,
|
|
HostID: "localhost",
|
|
NodeName: "node-name",
|
|
Role: types.RoleNode,
|
|
PublicSSHKey: sshPublicKey,
|
|
PublicTLSKey: tlsPublicKey,
|
|
},
|
|
},
|
|
{
|
|
desc: "reject wrong role dynamic",
|
|
req: &types.RegisterUsingTokenRequest{
|
|
Token: dynamicToken,
|
|
HostID: "localhost",
|
|
NodeName: "node-name",
|
|
Role: types.RoleProxy,
|
|
PublicSSHKey: sshPublicKey,
|
|
PublicTLSKey: tlsPublicKey,
|
|
},
|
|
errorAssertion: trace.IsBadParameter,
|
|
},
|
|
{
|
|
desc: "check additional principals",
|
|
req: &types.RegisterUsingTokenRequest{
|
|
Token: dynamicToken,
|
|
HostID: "localhost",
|
|
NodeName: "node-name",
|
|
Role: types.RoleNode,
|
|
PublicSSHKey: sshPublicKey,
|
|
PublicTLSKey: tlsPublicKey,
|
|
AdditionalPrincipals: []string{"example.com"},
|
|
},
|
|
certsAssertion: func(certs *proto.Certs) {
|
|
hostCert, err := sshutils.ParseCertificate(certs.SSH)
|
|
require.NoError(t, err)
|
|
require.Contains(t, hostCert.ValidPrincipals, "example.com")
|
|
},
|
|
},
|
|
{
|
|
desc: "reject expired dynamic token",
|
|
req: &types.RegisterUsingTokenRequest{
|
|
Token: dynamicToken,
|
|
HostID: "localhost",
|
|
NodeName: "node-name",
|
|
Role: types.RoleNode,
|
|
PublicSSHKey: sshPublicKey,
|
|
PublicTLSKey: tlsPublicKey,
|
|
},
|
|
waitTokenDeleted: true,
|
|
clock: clockwork.NewFakeClockAt(time.Now().Add(time.Hour + 1)),
|
|
errorAssertion: trace.IsAccessDenied,
|
|
},
|
|
{
|
|
// relies on token being deleted during previous testcase
|
|
desc: "expired token should be gone",
|
|
req: &types.RegisterUsingTokenRequest{
|
|
Token: dynamicToken,
|
|
HostID: "localhost",
|
|
NodeName: "node-name",
|
|
Role: types.RoleNode,
|
|
PublicSSHKey: sshPublicKey,
|
|
PublicTLSKey: tlsPublicKey,
|
|
},
|
|
clock: clockwork.NewRealClock(),
|
|
errorAssertion: trace.IsAccessDenied,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testcases {
|
|
t.Run(tc.desc, func(t *testing.T) {
|
|
if tc.clock == nil {
|
|
tc.clock = clockwork.NewRealClock()
|
|
}
|
|
a.SetClock(tc.clock)
|
|
certs, err := a.RegisterUsingToken(ctx, tc.req)
|
|
if tc.errorAssertion != nil {
|
|
require.True(t, tc.errorAssertion(err))
|
|
if tc.waitTokenDeleted {
|
|
require.Eventually(t, func() bool {
|
|
_, err := a.ValidateToken(ctx, tc.req.Token)
|
|
return err != nil && strings.Contains(err.Error(), TokenExpiredOrNotFound)
|
|
}, time.Millisecond*100, time.Millisecond*10)
|
|
}
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
if tc.certsAssertion != nil {
|
|
tc.certsAssertion(certs)
|
|
}
|
|
|
|
// Check audit log event is emitted
|
|
evt := p.mockEmitter.LastEvent()
|
|
require.NotNil(t, evt)
|
|
joinEvent, ok := evt.(*apievents.InstanceJoin)
|
|
require.True(t, ok)
|
|
require.Equal(t, events.InstanceJoinEvent, joinEvent.Type)
|
|
require.Equal(t, events.InstanceJoinCode, joinEvent.Code)
|
|
require.Equal(t, tc.req.NodeName, joinEvent.NodeName)
|
|
require.Equal(t, tc.req.HostID, joinEvent.HostID)
|
|
require.EqualValues(t, tc.req.Role, joinEvent.Role)
|
|
require.EqualValues(t, types.JoinMethodToken, joinEvent.Method)
|
|
})
|
|
}
|
|
}
|
|
|
|
func newBotToken(t *testing.T, tokenName, botName string, role types.SystemRole, expiry time.Time) types.ProvisionToken {
|
|
t.Helper()
|
|
token, err := types.NewProvisionTokenFromSpec(tokenName, expiry, types.ProvisionTokenSpecV2{
|
|
Roles: []types.SystemRole{role},
|
|
BotName: botName,
|
|
})
|
|
require.NoError(t, err, "could not create bot token")
|
|
return token
|
|
}
|
|
|
|
// TestRegister_Bot tests that a provision token can be used to generate
|
|
// renewable certificates for a non-interactive user.
|
|
func TestRegister_Bot(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
|
|
srv := newTestTLSServer(t)
|
|
|
|
botName := "test"
|
|
botResourceName := BotResourceName(botName)
|
|
|
|
_, err := createBotRole(ctx, srv.Auth(), botName, botResourceName, []string{})
|
|
require.NoError(t, err)
|
|
_, err = createBotUser(ctx, srv.Auth(), botName, botResourceName, wrappers.Traits{})
|
|
require.NoError(t, err)
|
|
|
|
later := srv.Clock().Now().Add(4 * time.Hour)
|
|
|
|
goodToken := newBotToken(t, "good-token", botName, types.RoleBot, later)
|
|
expiredToken := newBotToken(t, "expired", botName, types.RoleBot, srv.Clock().Now().Add(-1*time.Hour))
|
|
wrongKind := newBotToken(t, "wrong-kind", "", types.RoleNode, later)
|
|
wrongUser := newBotToken(t, "wrong-user", "llama", types.RoleBot, later)
|
|
invalidToken := newBotToken(t, "this-token-does-not-exist", botName, types.RoleBot, later)
|
|
|
|
err = srv.Auth().UpsertToken(ctx, goodToken)
|
|
require.NoError(t, err)
|
|
err = srv.Auth().UpsertToken(ctx, expiredToken)
|
|
require.NoError(t, err)
|
|
err = srv.Auth().UpsertToken(ctx, wrongKind)
|
|
require.NoError(t, err)
|
|
err = srv.Auth().UpsertToken(ctx, wrongUser)
|
|
require.NoError(t, err)
|
|
|
|
privateKey, publicKey, err := testauthority.New().GenerateKeyPair()
|
|
require.NoError(t, err)
|
|
sshPrivateKey, err := ssh.ParseRawPrivateKey(privateKey)
|
|
require.NoError(t, err)
|
|
tlsPublicKey, err := tlsca.MarshalPublicKeyFromPrivateKeyPEM(sshPrivateKey)
|
|
require.NoError(t, err)
|
|
|
|
for _, test := range []struct {
|
|
desc string
|
|
token types.ProvisionToken
|
|
assertErr require.ErrorAssertionFunc
|
|
}{
|
|
{
|
|
desc: "OK good token",
|
|
token: goodToken,
|
|
assertErr: require.NoError,
|
|
},
|
|
{
|
|
desc: "NOK expired token",
|
|
token: expiredToken,
|
|
assertErr: require.Error,
|
|
},
|
|
{
|
|
desc: "NOK wrong token kind",
|
|
token: wrongKind,
|
|
assertErr: require.Error,
|
|
},
|
|
{
|
|
desc: "NOK token for wrong user",
|
|
token: wrongUser,
|
|
assertErr: require.Error,
|
|
},
|
|
{
|
|
desc: "NOK invalid token",
|
|
token: invalidToken,
|
|
assertErr: require.Error,
|
|
},
|
|
} {
|
|
t.Run(test.desc, func(t *testing.T) {
|
|
start := srv.Clock().Now()
|
|
certs, err := Register(RegisterParams{
|
|
Token: test.token.GetName(),
|
|
ID: IdentityID{
|
|
Role: types.RoleBot,
|
|
},
|
|
AuthServers: []utils.NetAddr{*utils.MustParseAddr(srv.Addr().String())},
|
|
PublicTLSKey: tlsPublicKey,
|
|
PublicSSHKey: publicKey,
|
|
})
|
|
test.assertErr(t, err)
|
|
|
|
if err == nil {
|
|
require.NotEmpty(t, certs.SSH)
|
|
require.NotEmpty(t, certs.TLS)
|
|
|
|
// ensure token was removed
|
|
_, err = srv.Auth().GetToken(ctx, test.token.GetName())
|
|
require.True(t, trace.IsNotFound(err), "expected not found error, got %v", err)
|
|
|
|
// ensure cert is renewable
|
|
x509, err := tlsca.ParseCertificatePEM(certs.TLS)
|
|
require.NoError(t, err)
|
|
id, err := tlsca.FromSubject(x509.Subject, later)
|
|
require.NoError(t, err)
|
|
require.True(t, id.Renewable)
|
|
|
|
// Check audit event
|
|
evts, _, err := srv.Auth().SearchEvents(
|
|
start,
|
|
srv.Clock().Now(),
|
|
apidefaults.Namespace,
|
|
[]string{events.BotJoinEvent},
|
|
1,
|
|
types.EventOrderDescending,
|
|
"",
|
|
)
|
|
require.NoError(t, err)
|
|
require.Len(t, evts, 1)
|
|
evt, ok := evts[0].(*apievents.BotJoin)
|
|
require.True(t, ok)
|
|
require.Equal(t, events.BotJoinEvent, evt.Type)
|
|
require.Equal(t, events.BotJoinCode, evt.Code)
|
|
require.EqualValues(t, types.JoinMethodToken, evt.Method)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestRegister_Bot_Expiry checks that bot certificate expiry can be set, and
|
|
// does not exceed the limit.
|
|
func TestRegister_Bot_Expiry(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
|
|
srv := newTestTLSServer(t)
|
|
privateKey, publicKey, err := testauthority.New().GenerateKeyPair()
|
|
require.NoError(t, err)
|
|
sshPrivateKey, err := ssh.ParseRawPrivateKey(privateKey)
|
|
require.NoError(t, err)
|
|
tlsPublicKey, err := tlsca.MarshalPublicKeyFromPrivateKeyPEM(sshPrivateKey)
|
|
require.NoError(t, err)
|
|
|
|
validExpires := srv.Clock().Now().Add(time.Hour * 6)
|
|
tooGreatExpires := srv.Clock().Now().Add(time.Hour * 24 * 365)
|
|
tests := []struct {
|
|
name string
|
|
requestExpires *time.Time
|
|
expectTTL time.Duration
|
|
}{
|
|
{
|
|
name: "unspecified defaults",
|
|
requestExpires: nil,
|
|
expectTTL: defaults.DefaultRenewableCertTTL,
|
|
},
|
|
{
|
|
name: "valid value specified",
|
|
requestExpires: &validExpires,
|
|
expectTTL: time.Hour * 6,
|
|
},
|
|
{
|
|
name: "value exceeding limit specified",
|
|
requestExpires: &tooGreatExpires,
|
|
// MaxSessionTTL set in createBotRole is 12 hours, so this cap will
|
|
// apply instead of the defaults.MaxRenewableCertTTL specified
|
|
// in generateInitialBotCerts.
|
|
expectTTL: 12 * time.Hour,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
botName := t.Name()
|
|
botResourceName := BotResourceName(botName)
|
|
_, err := createBotRole(
|
|
ctx, srv.Auth(), botName, botResourceName, []string{},
|
|
)
|
|
require.NoError(t, err)
|
|
_, err = createBotUser(
|
|
ctx, srv.Auth(), botName, botResourceName, wrappers.Traits{},
|
|
)
|
|
require.NoError(t, err)
|
|
tok := newBotToken(t, t.Name(), botName, types.RoleBot, srv.Clock().Now().Add(time.Hour))
|
|
require.NoError(t, srv.Auth().UpsertToken(ctx, tok))
|
|
|
|
certs, err := Register(RegisterParams{
|
|
Token: tok.GetName(),
|
|
ID: IdentityID{
|
|
Role: types.RoleBot,
|
|
},
|
|
AuthServers: []utils.NetAddr{*utils.MustParseAddr(srv.Addr().String())},
|
|
PublicTLSKey: tlsPublicKey,
|
|
PublicSSHKey: publicKey,
|
|
Expires: tt.requestExpires,
|
|
})
|
|
require.NoError(t, err)
|
|
x509, err := tlsca.ParseCertificatePEM(certs.TLS)
|
|
require.NoError(t, err)
|
|
id, err := tlsca.FromSubject(x509.Subject, x509.NotAfter)
|
|
require.NoError(t, err)
|
|
|
|
ttl := id.Expires.Sub(srv.Clock().Now())
|
|
require.Equal(t, tt.expectTTL, ttl)
|
|
})
|
|
}
|
|
}
|