From 356407b4b1d421a6e815ed9e11233ee4d6910e89 Mon Sep 17 00:00:00 2001 From: Tim Buckley Date: Mon, 27 Jun 2022 17:21:25 -0600 Subject: [PATCH] Add Machine ID enterprise license enforcement This adds two checks to Machine ID for license enforcement: one on initial bot create, and another on join. --- lib/auth/bot.go | 11 +++++ lib/auth/bot_test.go | 106 +++++++++++++++++++++++++++++++++++++++++ lib/modules/modules.go | 1 + 3 files changed, 118 insertions(+) create mode 100644 lib/auth/bot_test.go diff --git a/lib/auth/bot.go b/lib/auth/bot.go index ad35da34fa84e..8b2c562e84252 100644 --- a/lib/auth/bot.go +++ b/lib/auth/bot.go @@ -32,6 +32,7 @@ import ( apievents "github.com/gravitational/teleport/api/types/events" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/events" + "github.com/gravitational/teleport/lib/modules" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/utils" ) @@ -112,6 +113,11 @@ func createBotUser(ctx context.Context, s *Server, botName string, resourceName // createBot creates a new certificate renewal bot from a bot request. func (s *Server) createBot(ctx context.Context, req *proto.CreateBotRequest) (*proto.CreateBotResponse, error) { + if !modules.GetModules().Features().MachineID { + return nil, trace.AccessDenied( + "this Teleport cluster is not licensed for Machine ID, please contact the cluster administrator") + } + if req.Name == "" { return nil, trace.BadParameter("bot name must not be empty") } @@ -453,6 +459,11 @@ func (s *Server) validateGenerationLabel(ctx context.Context, user types.User, c func (s *Server) generateInitialBotCerts(ctx context.Context, username string, pubKey []byte, expires time.Time, renewable bool) (*proto.Certs, error) { var err error + if !modules.GetModules().Features().MachineID { + return nil, trace.AccessDenied( + "this Teleport cluster is not licensed for Machine ID, please contact the cluster administrator") + } + // Extract the user and role set for whom the certificate will be generated. // This should be safe since this is typically done against a local user. // diff --git a/lib/auth/bot_test.go b/lib/auth/bot_test.go new file mode 100644 index 0000000000000..388d5f70846b7 --- /dev/null +++ b/lib/auth/bot_test.go @@ -0,0 +1,106 @@ +/* +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/teleport/api/client/proto" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/auth/native" + "github.com/gravitational/teleport/lib/modules" + "github.com/gravitational/teleport/lib/tlsca" + "github.com/gravitational/teleport/lib/utils" + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" + "golang.org/x/crypto/ssh" +) + +type botTestModules struct { + modules.Modules +} + +func (m *botTestModules) Features() modules.Features { + return modules.Features{ + MachineID: false, // Explicitly turn off Machine ID + } +} + +func TestBotCreateFeatureDisabled(t *testing.T) { + srv := newTestTLSServer(t) + + defaultModules := modules.GetModules() + defer modules.SetModules(defaultModules) + modules.SetModules(&botTestModules{}) + + _, err := CreateRole(context.Background(), srv.Auth(), "example", types.RoleSpecV5{}) + require.NoError(t, err) + + // Attempt to create a bot. This should fail immediately. + _, err = srv.Auth().createBot(context.Background(), &proto.CreateBotRequest{ + Name: "test", + Roles: []string{"example"}, + }) + require.True(t, trace.IsAccessDenied(err)) + require.Contains(t, err.Error(), "not licensed") +} + +func TestBotOnboardFeatureDisabled(t *testing.T) { + defaultModules := modules.GetModules() + defer modules.SetModules(defaultModules) + modules.SetModules(&botTestModules{}) + + 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) + require.NoError(t, err) + + later := srv.Clock().Now().Add(4 * time.Hour) + goodToken := newBotToken(t, "good-token", botName, types.RoleBot, later) + + err = srv.Auth().UpsertToken(context.Background(), goodToken) + require.NoError(t, err) + + privateKey, publicKey, err := native.GenerateKeyPair() + require.NoError(t, err) + sshPrivateKey, err := ssh.ParseRawPrivateKey(privateKey) + require.NoError(t, err) + tlsPublicKey, err := tlsca.MarshalPublicKeyFromPrivateKeyPEM(sshPrivateKey) + require.NoError(t, err) + + // Attempt to register a bot. This should fail even if a token was manually + // created. + _, err = Register(RegisterParams{ + Token: goodToken.GetName(), + ID: IdentityID{ + Role: types.RoleBot, + }, + Servers: []utils.NetAddr{*utils.MustParseAddr(srv.Addr().String())}, + PublicTLSKey: tlsPublicKey, + PublicSSHKey: publicKey, + }) + require.True(t, trace.IsAccessDenied(err)) + require.Contains(t, err.Error(), "not licensed") +} diff --git a/lib/modules/modules.go b/lib/modules/modules.go index c58a5fd93327c..9fadac1ed7a93 100644 --- a/lib/modules/modules.go +++ b/lib/modules/modules.go @@ -160,6 +160,7 @@ func (p *defaultModules) Features() Features { DB: true, App: true, Desktop: true, + MachineID: true, ModeratedSessions: false, // moderated sessions is supported in enterprise only } }