Skip to content

Commit

Permalink
Certificate renewal bot (#10099)
Browse files Browse the repository at this point in the history
* Add certificate renewal bot

This adds a new `tbot` tool to continuously renew a set of
certificates after registering with a Teleport cluster using a
similar process to standard node joining.

This makes some modifications to user certificate generation to allow
for certificates that can be renewed beyond their original TTL, and
exposes new gRPC endpoints:
 * `CreateBotJoinToken` creates a join token for a bot user
 * `GenerateInitialRenewableUserCerts` exchanges a token for a set of
   certificates with a new `renewable` flag set

A new `tctl` command, `tctl bots add`, creates a bot user and calls
`CreateBotJoinToken` to issue a token. A bot instance can then be
started using a provided command.

* Cert bot refactoring pass

* Use role requests to split renewable certs from end-user certs
* Add bot configuration file
* Use `teleport.dev/bot` label
* Remove `impersonator` flag on initial bot certs
* Remove unnecessary `renew` package
* Misc other cleanup

* Do not pass through `renewable` flag when role requests are set

This adds additional restrictions on when a certificate's `renewable`
flag is carried over to a new certificate. In particular, it now also
denies the flag when either role requests are present, or the
`disallowReissue` flag has been previously set.

In practice `disallow-reissue` would have prevented any undesired
behavior but this improves consistency and resolves a TODO.

* Various tbot UX improvements; render SSH config

* Fully flesh out config template rendering
* Fix rendering for SSH configuration templates
* Added `String()` impls for destination types
* Improve certificate renewal logging; show more detail
* Properly fall back to default (all) roles
* Add mode hints for files
* Add/update copyright headers

* Add stubs for tbot init and watch commands

* Add gRPC endpoints for managing bots

* Add `CreateBot`, `DeleteBot`, and `GetBotUsers` gRPC endpoints
* Replace `tctl bot (add|rm|ls)` implementations with gRPC calls
* Define a few new constants, `DefaultBotJoinTTL`, `BotLabel`,
  `BotGenerationLabel`

* Fix outdated destination flag in example tbot command

* Bugfix pass for demo

* Fixed a few nil pointer derefs when using config from CLI args
* Properly create destination if `--destination-dir` flag is used
* Remove improper default on CLI flag
* `DestinationConfig` is now a list of pointers

* Address first wave of review feedback

Fixes the majority of smaller issues caught by reviewers, thanks all!

* Add doc comments for bot.go functions

* Return the token TTL from CreateBot

* Split initial user cert issuance from `generateUserCerts()`

Issuing initial renewable certificate ended up requiring a lot of
hacks to skip checks that prevented anonymous bots from getting
certs even though we'd verified their identity elsewhere (via token).

This reverts all those hacks and splits initial bot cert logic into a
dedicated `generateInitialRenewableUserCerts()` function which should
make the whole process much easier to follow.

* Set bot traits to silence log messages

* tbot log message consistency pass

* Resolve lints

* Add config tests

* Remove CreateBotJoinToken endpoint

Users should instead use the CreateBot/DeleteBot endpoints.

* Create a fresh private key for every impersonated identity renewal

* Hide `config` subcommand

* Rename bot label prefix to `teleport.internal/`

* Use types.NewRole() to create bot roles

* Clean up error handling in custom YAML unmarshallers

Also, add notes about the supported YAML shapes.

* Fetch proxy host via gRPC Ping() instead of GetProxies()

* Update lib/auth/bot.go

Co-authored-by: Zac Bergquist <[email protected]>

* Fix some review comments

* Add renewable certificate generation checks (#10098)

* Add renewable certificate generation checks

This adds a new validation check for renewable certificates that
maintains a renewal counter as both a certificate extension and a
user label. This counter is used to ensure only a single certificate
lineage can exist: for example, if a renewable certificate is stolen,
only one copy of the certificate can be renewed as the generation
counter will not match

When renewing a certificate, first the generation counter presented
by the user (via their TLS identity) is compared to a value stored
with the associated user (in a new `teleport.dev/bot-generation`
label field). If they aren't equal, the renewal attempt fails.
Otherwise, the generation counter is incremented by 1, stored to the
database using a `CompareAndSwap()` to ensure atomicity, and set on
the generated certificate for use in future renewals.

* Add unit tests for the generation counter

This adds new unit tests to exercise the generation counter checks.

Additionally, it fixes two other renewable cert tests that were
failing.

* Remove certRequestGeneration() function

* Emit audit event when cert generations don't match

* Fully implement `tctl bots lock`

* Show bot name in `tctl bots ls`

* Lock bots when a cert generation mismatch is found

* Make CompareFailed respones from validateGenerationLabel() more actionable

* Update lib/services/local/users.go

Co-authored-by: Nic Klaassen <[email protected]>

* Backend changes for tbot IoT and AWS joining (#10360)

* backend changes

* add token permission check

* pass ctx from caller

Co-authored-by: Roman Tkachenko <[email protected]>

* fix comment typo

Co-authored-by: Roman Tkachenko <[email protected]>

* use UserMetadata instead of Identity in RenewableCertificateGenerationMismatch event

* Client changes for tbot IoT joining (#10397)

* client changes

* delete replaced APIs

* delete unused tbot/auth.go

* add license header

* don't unecessarily fetch host CA

* log fixes

* s/tunnelling/tunneling/

Co-authored-by: Zac Bergquist <[email protected]>

* auth server addresses may be proxies

Co-authored-by: Zac Bergquist <[email protected]>

* comment typo fix

Co-authored-by: Zac Bergquist <[email protected]>

* move *Server methods out of auth_with_roles.go (#10416)

Co-authored-by: Tim Buckley <[email protected]>

Co-authored-by: Zac Bergquist <[email protected]>
Co-authored-by: Tim Buckley <[email protected]>

Co-authored-by: Roman Tkachenko <[email protected]>
Co-authored-by: Tim Buckley <[email protected]>
Co-authored-by: Zac Bergquist <[email protected]>

Co-authored-by: Nic Klaassen <[email protected]>
Co-authored-by: Roman Tkachenko <[email protected]>
Co-authored-by: Zac Bergquist <[email protected]>

* Address another batch of review feedback

* Addres another batch of review feedback

Add `Role.SetMetadata()`, simplify more `trace.WrapWithMessage()`
calls, clear some TODOs and lints, and address other misc feedback
items.

* Fix lint

* Add missing doc comments to SaveIdentity / LoadIdentity

* Remove pam tag from tbot build

* Update note about bot lock deletion

* Another pass of review feedback

Ensure all requestable roles exist when creating a bot, adjust the
default renewable cert TTL down to 1 hour, and check types during
`CompareAndSwapUser()`

Co-authored-by: Zac Bergquist <[email protected]>
Co-authored-by: Nic Klaassen <[email protected]>
Co-authored-by: Roman Tkachenko <[email protected]>
  • Loading branch information
4 people authored Feb 19, 2022
1 parent ac9803f commit bb121d7
Show file tree
Hide file tree
Showing 63 changed files with 7,726 additions and 2,185 deletions.
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ endif

# On Windows only build tsh. On all other platforms build teleport, tctl,
# and tsh.
BINARIES=$(BUILDDIR)/teleport $(BUILDDIR)/tctl $(BUILDDIR)/tsh
BINARIES=$(BUILDDIR)/teleport $(BUILDDIR)/tctl $(BUILDDIR)/tsh $(BUILDDIR)/tbot
RELEASE_MESSAGE := "Building with GOOS=$(OS) GOARCH=$(ARCH) REPRODUCIBLE=$(REPRODUCIBLE) and $(PAM_MESSAGE) and $(FIPS_MESSAGE) and $(BPF_MESSAGE) and $(ROLETESTER_MESSAGE) and $(RDPCLIENT_MESSAGE)."
ifeq ("$(OS)","windows")
BINARIES=$(BUILDDIR)/tsh
Expand Down Expand Up @@ -208,6 +208,10 @@ $(BUILDDIR)/teleport: ensure-webassets bpf-bytecode rdpclient
$(BUILDDIR)/tsh:
GOOS=$(OS) GOARCH=$(ARCH) $(CGOFLAG_TSH) go build -tags "$(PAM_TAG) $(FIPS_TAG)" -o $(BUILDDIR)/tsh $(BUILDFLAGS) ./tool/tsh

.PHONY: $(BUILDDIR)/tbot
$(BUILDDIR)/tbot:
GOOS=$(OS) GOARCH=$(ARCH) $(CGOFLAG) go build -tags "$(FIPS_TAG)" -o $(BUILDDIR)/tbot $(BUILDFLAGS) ./tool/tbot

#
# BPF support (IF ENABLED)
# Requires a recent version of clang and libbpf installed.
Expand Down
34 changes: 34 additions & 0 deletions api/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,40 @@ func (c *Client) CreateResetPasswordToken(ctx context.Context, req *proto.Create
return token, nil
}

// CreateBot creates a new bot from the specified descriptor.
func (c *Client) CreateBot(ctx context.Context, req *proto.CreateBotRequest) (*proto.CreateBotResponse, error) {
response, err := c.grpc.CreateBot(ctx, req, c.callOpts...)
if err != nil {
return nil, trail.FromGRPC(err)
}

return response, nil
}

// DeleteBot deletes a bot and associated resources.
func (c *Client) DeleteBot(ctx context.Context, botName string) error {
_, err := c.grpc.DeleteBot(ctx, &proto.DeleteBotRequest{
Name: botName,
}, c.callOpts...)
return trail.FromGRPC(err)
}

// GetBotUsers fetches all bot users.
func (c *Client) GetBotUsers(ctx context.Context) ([]types.User, error) {
stream, err := c.grpc.GetBotUsers(ctx, &proto.GetBotUsersRequest{}, c.callOpts...)
if err != nil {
return nil, trail.FromGRPC(err)
}
var users []types.User
for user, err := stream.Recv(); err != io.EOF; user, err = stream.Recv() {
if err != nil {
return nil, trail.FromGRPC(err)
}
users = append(users, user)
}
return users, nil
}

// GetAccessRequests retrieves a list of all access requests matching the provided filter.
func (c *Client) GetAccessRequests(ctx context.Context, filter types.AccessRequestFilter) ([]types.AccessRequest, error) {
rsp, err := c.grpc.GetAccessRequests(ctx, &filter, c.callOpts...)
Expand Down
Loading

0 comments on commit bb121d7

Please sign in to comment.