Files
teleport/lib/auth/join_test.go
T
2022-10-03 14:14:01 +00:00

370 lines
10 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"
"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"
"github.com/gravitational/teleport/api/types"
"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/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, err := a.GenerateToken(ctx, &proto.GenerateTokenRequest{
Roles: types.SystemRoles{types.RoleNode},
TTL: proto.Duration(time.Hour),
})
require.NoError(t, err)
require.NotNil(t, dynamicToken)
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
}{
{
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 pricipals",
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,
},
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))
return
}
require.NoError(t, err)
if tc.certsAssertion != nil {
tc.certsAssertion(certs)
}
})
}
}
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()
srv := newTestTLSServer(t)
botName := "test"
botResourceName := BotResourceName(botName)
_, err := createBotRole(context.Background(), srv.Auth(), "test", "bot-test", []string{})
require.NoError(t, err)
_, err = createBotUser(context.Background(), 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(context.Background(), goodToken)
require.NoError(t, err)
err = srv.Auth().UpsertToken(context.Background(), expiredToken)
require.NoError(t, err)
err = srv.Auth().UpsertToken(context.Background(), wrongKind)
require.NoError(t, err)
err = srv.Auth().UpsertToken(context.Background(), 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) {
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(context.Background(), 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)
}
})
}
}