mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-31 20:39:37 +08:00
This PR includes a new Role resource version that is compatible with V5 spec.
The new resource introduces the `kubernetes_resources` definition that allows operators to limit the Kubernetes resources that each member can access. The `kubernetes_resources` entries must follow the following format: `{"kind":"<kind>", "namespace":"<namespace>","name":"<pod>"}`. Currently, it only supports objects of `kind` `pod`. Valid examples `<namespace>/<name>:
- `*/*`: matches all pods in all namespaces.
- `default/*`: matches all pods in the `default` namespace.
- `*/nginx-*`: matches every pod prefixed with `nginx-` in every namespace.
For older resource versions - V5, V4, V3 - `kubernetes_resources` is automatically populated with `{"kind":"pod","namespace":"*","name":"*"}` to keep compatibility. For the newest version, it's mandatory to define its value otherwise access to pods will be denied.
Part of #18434
310 lines
9.1 KiB
Go
310 lines
9.1 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"
|
|
"crypto/tls"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gravitational/trace"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/crypto/ssh"
|
|
|
|
"github.com/gravitational/teleport/api/client/proto"
|
|
"github.com/gravitational/teleport/api/constants"
|
|
"github.com/gravitational/teleport/api/types"
|
|
"github.com/gravitational/teleport/api/types/wrappers"
|
|
"github.com/gravitational/teleport/lib/auth/testauthority"
|
|
"github.com/gravitational/teleport/lib/tlsca"
|
|
"github.com/gravitational/teleport/lib/utils"
|
|
)
|
|
|
|
// TestServerCreateBot ensures that the create bot RPC creates the appropriate
|
|
// role and users.
|
|
//
|
|
// TODO: We should add more cases to this to properly exercise the token
|
|
// creation elements of createBot.
|
|
func TestServerCreateBot(t *testing.T) {
|
|
t.Parallel()
|
|
srv := newTestTLSServer(t)
|
|
ctx := context.Background()
|
|
testRole := "test-role"
|
|
_, err := CreateRole(ctx, srv.Auth(), testRole, types.RoleSpecV6{})
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
request *proto.CreateBotRequest
|
|
|
|
checkErr func(*testing.T, error)
|
|
|
|
checkUser func(*testing.T, types.User)
|
|
checkRole func(*testing.T, types.Role)
|
|
}{
|
|
{
|
|
name: "no roles",
|
|
request: &proto.CreateBotRequest{
|
|
Name: "no-roles",
|
|
},
|
|
checkErr: func(t *testing.T, err error) {
|
|
require.ErrorContains(t, err, "at least one role is required")
|
|
require.True(t, trace.IsBadParameter(err))
|
|
},
|
|
},
|
|
{
|
|
name: "success",
|
|
request: &proto.CreateBotRequest{
|
|
Name: "success",
|
|
Roles: []string{testRole},
|
|
Traits: wrappers.Traits{
|
|
constants.TraitLogins: []string{
|
|
"a-principal",
|
|
},
|
|
},
|
|
},
|
|
checkUser: func(t *testing.T, got types.User) {
|
|
require.Equal(t, []string{"bot-success"}, got.GetRoles())
|
|
require.Equal(t, map[string]string{
|
|
types.BotLabel: "success",
|
|
types.BotGenerationLabel: "0",
|
|
}, got.GetMetadata().Labels)
|
|
// Ensure bot user receives requested traits
|
|
require.Equal(
|
|
t,
|
|
[]string{"a-principal"},
|
|
got.GetTraits()[constants.TraitLogins],
|
|
)
|
|
},
|
|
checkRole: func(t *testing.T, got types.Role) {
|
|
require.Equal(
|
|
t, "success", got.GetMetadata().Labels[types.BotLabel],
|
|
)
|
|
require.Equal(
|
|
t,
|
|
[]string{testRole},
|
|
got.GetImpersonateConditions(types.Allow).Roles,
|
|
)
|
|
require.Equal(
|
|
t,
|
|
types.Duration(12*time.Hour),
|
|
got.GetOptions().MaxSessionTTL,
|
|
)
|
|
// Ensure bot will be able to read the cert authorities
|
|
require.Equal(
|
|
t,
|
|
[]types.Rule{
|
|
types.NewRule(
|
|
types.KindCertAuthority,
|
|
[]string{types.VerbReadNoSecrets},
|
|
),
|
|
},
|
|
got.GetRules(types.Allow),
|
|
)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
ctx := context.Background()
|
|
res, err := srv.Auth().createBot(ctx, tt.request)
|
|
if tt.checkErr != nil {
|
|
tt.checkErr(t, err)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
// Ensure createBot produces the expected role and user.
|
|
resourceName := BotResourceName(tt.request.Name)
|
|
usr, err := srv.Auth().Services.GetUser(resourceName, false)
|
|
require.NoError(t, err)
|
|
tt.checkUser(t, usr)
|
|
role, err := srv.Auth().Services.GetRole(ctx, resourceName)
|
|
require.NoError(t, err)
|
|
tt.checkRole(t, role)
|
|
|
|
// Ensure response includes the correct details
|
|
require.Equal(t, resourceName, res.UserName)
|
|
require.Equal(t, resourceName, res.RoleName)
|
|
require.Equal(t, types.JoinMethodToken, res.JoinMethod)
|
|
|
|
// Check generated token exists
|
|
token, err := srv.Auth().Services.GetToken(ctx, res.TokenID)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.request.Name, token.GetBotName())
|
|
require.Equal(t, types.JoinMethodToken, token.GetJoinMethod())
|
|
require.Equal(t, types.SystemRoles{types.RoleBot}, token.GetRoles())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBotResourceName(t *testing.T) {
|
|
require.Equal(t, "bot-name", BotResourceName("name"))
|
|
require.Equal(t, "bot-name-with-spaces", BotResourceName("name with spaces"))
|
|
}
|
|
|
|
func renewBotCerts(
|
|
ctx context.Context,
|
|
srv *TestTLSServer,
|
|
tlsCert tls.Certificate,
|
|
botUser string,
|
|
publicKey []byte,
|
|
privateKey []byte,
|
|
) (*Client, *proto.Certs, tls.Certificate, error) {
|
|
client := srv.NewClientWithCert(tlsCert)
|
|
|
|
certs, err := client.GenerateUserCerts(ctx, proto.UserCertsRequest{
|
|
PublicKey: publicKey,
|
|
Username: botUser,
|
|
Expires: time.Now().Add(time.Hour),
|
|
})
|
|
if err != nil {
|
|
return nil, nil, tls.Certificate{}, trace.Wrap(err)
|
|
}
|
|
|
|
// Make sure to overwrite tlsCert with the new certs.
|
|
tlsCert, err = tls.X509KeyPair(certs.TLS, privateKey)
|
|
if err != nil {
|
|
return nil, nil, tls.Certificate{}, trace.Wrap(err)
|
|
}
|
|
|
|
return client, certs, tlsCert, nil
|
|
}
|
|
|
|
// TestRegisterBotCertificateGenerationCheck ensures bot cert generation checks
|
|
// work in ordinary conditions, with several rapid renewals.
|
|
func TestRegisterBotCertificateGenerationCheck(t *testing.T) {
|
|
t.Parallel()
|
|
srv := newTestTLSServer(t)
|
|
ctx := context.Background()
|
|
|
|
_, err := CreateRole(ctx, srv.Auth(), "example", types.RoleSpecV6{})
|
|
require.NoError(t, err)
|
|
|
|
// Create a new bot.
|
|
bot, err := srv.Auth().createBot(ctx, &proto.CreateBotRequest{
|
|
Name: "test",
|
|
Roles: []string{"example"},
|
|
})
|
|
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)
|
|
|
|
certs, err := Register(RegisterParams{
|
|
Token: bot.TokenID,
|
|
ID: IdentityID{
|
|
Role: types.RoleBot,
|
|
},
|
|
AuthServers: []utils.NetAddr{*utils.MustParseAddr(srv.Addr().String())},
|
|
PublicTLSKey: tlsPublicKey,
|
|
PublicSSHKey: publicKey,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
tlsCert, err := tls.X509KeyPair(certs.TLS, privateKey)
|
|
require.NoError(t, err)
|
|
|
|
// Renew the cert a bunch of times.
|
|
for i := 0; i < 10; i++ {
|
|
_, certs, tlsCert, err = renewBotCerts(ctx, srv, tlsCert, bot.UserName, publicKey, privateKey)
|
|
require.NoError(t, err)
|
|
|
|
// Parse the Identity
|
|
impersonatedTLSCert, err := tlsca.ParseCertificatePEM(certs.TLS)
|
|
require.NoError(t, err)
|
|
impersonatedIdent, err := tlsca.FromSubject(impersonatedTLSCert.Subject, impersonatedTLSCert.NotAfter)
|
|
require.NoError(t, err)
|
|
|
|
// Cert must be renewable.
|
|
require.True(t, impersonatedIdent.Renewable)
|
|
require.False(t, impersonatedIdent.DisallowReissue)
|
|
|
|
// Initial certs have generation=1 and we start the loop with a renewal, so add 2
|
|
require.Equal(t, uint64(i+2), impersonatedIdent.Generation)
|
|
}
|
|
}
|
|
|
|
// TestRegisterBotCertificateGenerationStolen simulates a stolen renewable
|
|
// certificate where a generation check is expected to fail.
|
|
func TestRegisterBotCertificateGenerationStolen(t *testing.T) {
|
|
t.Parallel()
|
|
srv := newTestTLSServer(t)
|
|
ctx := context.Background()
|
|
_, err := CreateRole(ctx, srv.Auth(), "example", types.RoleSpecV6{})
|
|
require.NoError(t, err)
|
|
|
|
// Create a new bot.
|
|
bot, err := srv.Auth().createBot(ctx, &proto.CreateBotRequest{
|
|
Name: "test",
|
|
Roles: []string{"example"},
|
|
})
|
|
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)
|
|
|
|
certs, err := Register(RegisterParams{
|
|
Token: bot.TokenID,
|
|
ID: IdentityID{
|
|
Role: types.RoleBot,
|
|
},
|
|
AuthServers: []utils.NetAddr{*utils.MustParseAddr(srv.Addr().String())},
|
|
PublicTLSKey: tlsPublicKey,
|
|
PublicSSHKey: publicKey,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
tlsCert, err := tls.X509KeyPair(certs.TLS, privateKey)
|
|
require.NoError(t, err)
|
|
|
|
// Renew the certs once (e.g. this is the actual bot process)
|
|
_, certsReal, _, err := renewBotCerts(ctx, srv, tlsCert, bot.UserName, publicKey, privateKey)
|
|
require.NoError(t, err)
|
|
|
|
// Check the generation, it should be 2.
|
|
impersonatedTLSCert, err := tlsca.ParseCertificatePEM(certsReal.TLS)
|
|
require.NoError(t, err)
|
|
impersonatedIdent, err := tlsca.FromSubject(impersonatedTLSCert.Subject, impersonatedTLSCert.NotAfter)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(2), impersonatedIdent.Generation)
|
|
|
|
// Meanwhile, the initial set of certs was stolen. Let's try to renew those.
|
|
_, _, _, err = renewBotCerts(ctx, srv, tlsCert, bot.UserName, publicKey, privateKey)
|
|
require.Error(t, err)
|
|
require.True(t, trace.IsAccessDenied(err))
|
|
|
|
// The user should now be locked.
|
|
locks, err := srv.Auth().GetLocks(ctx, true, types.LockTarget{
|
|
User: "bot-test",
|
|
})
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, locks)
|
|
}
|