Skip to content

Commit

Permalink
Add static host users gRPC service (#45292)
Browse files Browse the repository at this point in the history
This change adds the gRPC service for static host users.
  • Loading branch information
atburke committed Sep 10, 2024
1 parent 99e7ea4 commit f9127a4
Show file tree
Hide file tree
Showing 7 changed files with 687 additions and 0 deletions.
7 changes: 7 additions & 0 deletions api/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import (
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/client/scim"
"github.com/gravitational/teleport/api/client/secreport"
statichostuserclient "github.com/gravitational/teleport/api/client/statichostuser"
"github.com/gravitational/teleport/api/client/userloginstate"
"github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/defaults"
Expand Down Expand Up @@ -84,6 +85,7 @@ import (
secreportsv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/secreports/v1"
trustpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/trust/v1"
userloginstatev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/userloginstate/v1"
userprovisioningpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/userprovisioning/v1"
userspb "github.com/gravitational/teleport/api/gen/proto/go/teleport/users/v1"
"github.com/gravitational/teleport/api/gen/proto/go/teleport/vnet/v1"
userpreferencespb "github.com/gravitational/teleport/api/gen/proto/go/userpreferences/v1"
Expand Down Expand Up @@ -3264,6 +3266,11 @@ func (c *Client) DeleteKubernetesWaitingContainer(ctx context.Context, req *kube
return c.GetKubernetesWaitingContainerClient().DeleteKubernetesWaitingContainer(ctx, req)
}

// StaticHostUserClient returns a new static host user client.
func (c *Client) StaticHostUserClient() *statichostuserclient.Client {
return statichostuserclient.NewClient(userprovisioningpb.NewStaticHostUsersServiceClient(c.conn))
}

// CreateDatabase creates a new database resource.
func (c *Client) CreateDatabase(ctx context.Context, database types.Database) error {
databaseV3, ok := database.(*types.DatabaseV3)
Expand Down
94 changes: 94 additions & 0 deletions api/client/statichostuser/statichostuser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright 2024 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 statichostuser

import (
"context"

"github.com/gravitational/trace"

userprovisioningpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/userprovisioning/v1"
)

// Client is a StaticHostUser client.
type Client struct {
grpcClient userprovisioningpb.StaticHostUsersServiceClient
}

// NewClient creates a new StaticHostUser client.
func NewClient(grpcClient userprovisioningpb.StaticHostUsersServiceClient) *Client {
return &Client{
grpcClient: grpcClient,
}
}

// ListStaticHostUsers lists static host users.
func (c *Client) ListStaticHostUsers(ctx context.Context, pageSize int, pageToken string) ([]*userprovisioningpb.StaticHostUser, string, error) {
resp, err := c.grpcClient.ListStaticHostUsers(ctx, &userprovisioningpb.ListStaticHostUsersRequest{
PageSize: int32(pageSize),
PageToken: pageToken,
})
if err != nil {
return nil, "", trace.Wrap(err)
}
return resp.Users, resp.NextPageToken, nil
}

// GetStaticHostUser returns a static host user by name.
func (c *Client) GetStaticHostUser(ctx context.Context, name string) (*userprovisioningpb.StaticHostUser, error) {
if name == "" {
return nil, trace.BadParameter("missing name")
}
out, err := c.grpcClient.GetStaticHostUser(ctx, &userprovisioningpb.GetStaticHostUserRequest{
Name: name,
})
return out, trace.Wrap(err)
}

// CreateStaticHostUser creates a static host user.
func (c *Client) CreateStaticHostUser(ctx context.Context, in *userprovisioningpb.StaticHostUser) (*userprovisioningpb.StaticHostUser, error) {
out, err := c.grpcClient.CreateStaticHostUser(ctx, &userprovisioningpb.CreateStaticHostUserRequest{
User: in,
})
return out, trace.Wrap(err)
}

// UpdateStaticHostUser updates a static host user.
func (c *Client) UpdateStaticHostUser(ctx context.Context, in *userprovisioningpb.StaticHostUser) (*userprovisioningpb.StaticHostUser, error) {
out, err := c.grpcClient.UpdateStaticHostUser(ctx, &userprovisioningpb.UpdateStaticHostUserRequest{
User: in,
})
return out, trace.Wrap(err)
}

// UpsertStaticHostUser upserts a static host user.
func (c *Client) UpsertStaticHostUser(ctx context.Context, in *userprovisioningpb.StaticHostUser) (*userprovisioningpb.StaticHostUser, error) {
out, err := c.grpcClient.UpsertStaticHostUser(ctx, &userprovisioningpb.UpsertStaticHostUserRequest{
User: in,
})
return out, trace.Wrap(err)
}

// DeleteStaticHostUser deletes a static host user. Note that this does not
// remove any host users created on nodes from the resource.
func (c *Client) DeleteStaticHostUser(ctx context.Context, name string) error {
if name == "" {
return trace.BadParameter("missing name")
}
_, err := c.grpcClient.DeleteStaticHostUser(ctx, &userprovisioningpb.DeleteStaticHostUserRequest{
Name: name,
})
return trace.Wrap(err)
}
9 changes: 9 additions & 0 deletions lib/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,13 @@ func NewServer(cfg *InitConfig, opts ...ServerOption) (*Server, error) {
}
}

if cfg.StaticHostUsers == nil {
cfg.StaticHostUsers, err = local.NewStaticHostUserService(cfg.Backend)
if err != nil {
return nil, trace.Wrap(err)
}
}

closeCtx, cancelFunc := context.WithCancel(context.TODO())
services := &Services{
TrustInternal: cfg.Trust,
Expand Down Expand Up @@ -430,6 +437,7 @@ func NewServer(cfg *InitConfig, opts ...ServerOption) (*Server, error) {
CrownJewels: cfg.CrownJewels,
BotInstance: cfg.BotInstance,
SPIFFEFederations: cfg.SPIFFEFederations,
StaticHostUser: cfg.StaticHostUsers,
}

as := Server{
Expand Down Expand Up @@ -627,6 +635,7 @@ type Services struct {
services.AccessGraphSecretsGetter
services.DevicesGetter
services.BotInstance
services.StaticHostUser
}

// GetWebSession returns existing web session described by req.
Expand Down
11 changes: 11 additions & 0 deletions lib/auth/grpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import (
presencev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/presence/v1"
trustpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/trust/v1"
userloginstatev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/userloginstate/v1"
userprovisioningpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/userprovisioning/v1"
userspb "github.com/gravitational/teleport/api/gen/proto/go/teleport/users/v1"
"github.com/gravitational/teleport/api/gen/proto/go/teleport/vnet/v1"
userpreferencespb "github.com/gravitational/teleport/api/gen/proto/go/userpreferences/v1"
Expand All @@ -89,6 +90,7 @@ import (
notifications "github.com/gravitational/teleport/lib/auth/notifications/notificationsv1"
"github.com/gravitational/teleport/lib/auth/okta"
"github.com/gravitational/teleport/lib/auth/presence/presencev1"
statichostuserv1 "github.com/gravitational/teleport/lib/auth/statichostuser"
"github.com/gravitational/teleport/lib/auth/trust/trustv1"
"github.com/gravitational/teleport/lib/auth/userloginstate"
"github.com/gravitational/teleport/lib/auth/userpreferences/userpreferencesv1"
Expand Down Expand Up @@ -5422,6 +5424,15 @@ func NewGRPCServer(cfg GRPCServerConfig) (*GRPCServer, error) {
vnetConfigServiceServer := vnetconfig.NewService(vnetConfigStorage, cfg.Authorizer)
vnet.RegisterVnetConfigServiceServer(server, vnetConfigServiceServer)

staticHostUserServer, err := statichostuserv1.NewService(statichostuserv1.ServiceConfig{
Authorizer: cfg.Authorizer,
Backend: cfg.AuthServer.Services,
})
if err != nil {
return nil, trace.Wrap(err)
}
userprovisioningpb.RegisterStaticHostUsersServiceServer(server, staticHostUserServer)

// Only register the service if this is an open source build. Enterprise builds
// register the actual service via an auth plugin, if we register here then all
// Enterprise builds would fail with a duplicate service registered error.
Expand Down
4 changes: 4 additions & 0 deletions lib/auth/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,10 @@ type InitConfig struct {

// SPIFFEFederations is a service that manages storing SPIFFE federations.
SPIFFEFederations services.SPIFFEFederations

// StaticHostUsers is a service that manages host users that should be
// created on SSH nodes.
StaticHostUsers services.StaticHostUser
}

// Init instantiates and configures an instance of AuthServer
Expand Down
176 changes: 176 additions & 0 deletions lib/auth/statichostuser/service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Teleport
// Copyright (C) 2024 Gravitational, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package statichostuser

import (
"context"

"github.com/gravitational/trace"
"google.golang.org/protobuf/types/known/emptypb"

userprovisioningpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/userprovisioning/v1"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/authz"
"github.com/gravitational/teleport/lib/services"
)

// ServiceConfig holds configuration options for the static host user gRPC service.
type ServiceConfig struct {
// Authorizer is the authorizer used to check access to resources.
Authorizer authz.Authorizer
// Backend is the backend used to store static host users.
Backend services.StaticHostUser
// TODO(atburke): add cache
}

// Service implements the static host user RPC service.
type Service struct {
userprovisioningpb.UnimplementedStaticHostUsersServiceServer

authorizer authz.Authorizer
backend services.StaticHostUser
}

// NewService creates a new static host user gRPC service.
func NewService(cfg ServiceConfig) (*Service, error) {
switch {
case cfg.Backend == nil:
return nil, trace.BadParameter("backend is required")
case cfg.Authorizer == nil:
return nil, trace.BadParameter("authorizer is required")
}

return &Service{
authorizer: cfg.Authorizer,
backend: cfg.Backend,
}, nil
}

// ListStaticHostUsers lists static host users.
func (s *Service) ListStaticHostUsers(ctx context.Context, req *userprovisioningpb.ListStaticHostUsersRequest) (*userprovisioningpb.ListStaticHostUsersResponse, error) {
authCtx, err := s.authorizer.Authorize(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
if err := authCtx.CheckAccessToKind(types.KindStaticHostUser, types.VerbList, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
if err := authCtx.AuthorizeAdminAction(); err != nil {
return nil, trace.Wrap(err)
}

// TODO(atburke): Switch to using the cache after static host users have been added to the cache.
users, nextToken, err := s.backend.ListStaticHostUsers(ctx, int(req.PageSize), req.PageToken)
if err != nil {
return nil, trace.Wrap(err)
}
return &userprovisioningpb.ListStaticHostUsersResponse{
Users: users,
NextPageToken: nextToken,
}, nil
}

// GetStaticHostUser returns a static host user by name.
func (s *Service) GetStaticHostUser(ctx context.Context, req *userprovisioningpb.GetStaticHostUserRequest) (*userprovisioningpb.StaticHostUser, error) {
if req.Name == "" {
return nil, trace.BadParameter("missing name")
}
authCtx, err := s.authorizer.Authorize(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
if err := authCtx.CheckAccessToKind(types.KindStaticHostUser, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
if err := authCtx.AuthorizeAdminAction(); err != nil {
return nil, trace.Wrap(err)
}

// TODO(atburke): Switch to using the cache after static host users have been added to the cache.
out, err := s.backend.GetStaticHostUser(ctx, req.Name)
return out, trace.Wrap(err)
}

// CreateStaticHostUser creates a static host user.
func (s *Service) CreateStaticHostUser(ctx context.Context, req *userprovisioningpb.CreateStaticHostUserRequest) (*userprovisioningpb.StaticHostUser, error) {
authCtx, err := s.authorizer.Authorize(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
if err := authCtx.CheckAccessToKind(types.KindStaticHostUser, types.VerbCreate); err != nil {
return nil, trace.Wrap(err)
}
if err := authCtx.AuthorizeAdminAction(); err != nil {
return nil, trace.Wrap(err)
}

out, err := s.backend.CreateStaticHostUser(ctx, req.User)
return out, trace.Wrap(err)
}

// UpdateStaticHostUser updates a static host user.
func (s *Service) UpdateStaticHostUser(ctx context.Context, req *userprovisioningpb.UpdateStaticHostUserRequest) (*userprovisioningpb.StaticHostUser, error) {
authCtx, err := s.authorizer.Authorize(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
if err := authCtx.CheckAccessToKind(types.KindStaticHostUser, types.VerbUpdate); err != nil {
return nil, trace.Wrap(err)
}
if err := authCtx.AuthorizeAdminAction(); err != nil {
return nil, trace.Wrap(err)
}

out, err := s.backend.UpdateStaticHostUser(ctx, req.User)
return out, trace.Wrap(err)
}

// UpsertStaticHostUser upserts a static host user.
func (s *Service) UpsertStaticHostUser(ctx context.Context, req *userprovisioningpb.UpsertStaticHostUserRequest) (*userprovisioningpb.StaticHostUser, error) {
authCtx, err := s.authorizer.Authorize(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
if err := authCtx.CheckAccessToKind(types.KindStaticHostUser, types.VerbCreate, types.VerbUpdate); err != nil {
return nil, trace.Wrap(err)
}
if err := authCtx.AuthorizeAdminAction(); err != nil {
return nil, trace.Wrap(err)
}

out, err := s.backend.UpsertStaticHostUser(ctx, req.User)
return out, trace.Wrap(err)
}

// DeleteStaticHostUser deletes a static host user. Note that this does not
// remove any host users created on nodes from the resource.
func (s *Service) DeleteStaticHostUser(ctx context.Context, req *userprovisioningpb.DeleteStaticHostUserRequest) (*emptypb.Empty, error) {
if req.Name == "" {
return nil, trace.BadParameter("missing name")
}
authCtx, err := s.authorizer.Authorize(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
if err := authCtx.CheckAccessToKind(types.KindStaticHostUser, types.VerbDelete); err != nil {
return nil, trace.Wrap(err)
}
if err := authCtx.AuthorizeAdminAction(); err != nil {
return nil, trace.Wrap(err)
}
return &emptypb.Empty{}, trace.Wrap(s.backend.DeleteStaticHostUser(ctx, req.Name))
}
Loading

0 comments on commit f9127a4

Please sign in to comment.