Files
teleport/lib/auth/join_test.go
T
Anton Miniailo 9c600393c2 Add delay for the test when expired token is deleted in background (#22383)
* Add delay for the test when expired token is deleted in background

* Use require.Eventually
2023-02-28 22:22:57 +00:00

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)
})
}
}