Files
teleport/lib/services/provisioning.go
T
Dan Upton 580326165b Add Beam protobuf definition and backend service (#64863)
* Add `Beam` resource protobuf definition

* Add beams backend service

* Fix test and lint failures

* Delete all beam alias leases on `DeleteAllBeams`

* Apply PR feedback

* Restructure beam storage service

Following comments on the PR and a Slack conversation with Edorado, this commit
restructures the storage service interface to reflect how it'll actually be used.

For example, we now write the beam and its supporting resources in one atomic
operation, rather than lots of little fallible writes.

It also incorporates other feedback such as adding a field to track the
provision state of the actual beam VM.

* Use `types.GetExpiry` helper to avoid protobuf epoch issues

* Fix linting errors

* Store `DelegationSession` with beam resources

Also remove duplicate `itemFromWorkloadIdentity` definition

* Remove cross-service dependency

Following @espadolini's comment, the `BeamService` is no longer responsible for
writing the actual node, app, token, etc. records to the backend. Instead, those
services expose `AppendPut*Actions` and `AppendDelete*Actions` methods and it's
the caller's responsibility to call `Backend.AtomicWrite` with them.

* Move `Append*Actions` methods to "internal" service interfaces

* Fix test errors in tests

* Store users without secrets

* Rename user methods for clarity about what's included or not

* Fix accidental cache usage

* Fix broken reference from master

* Do not set an expiry on beam alias

In case the alias gets reassigned before the original beam is cleaned up
2026-04-08 11:25:06 +00:00

211 lines
7.0 KiB
Go

/*
* Teleport
* Copyright (C) 2023 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 services
import (
"context"
"strings"
"time"
"github.com/gravitational/trace"
"github.com/gravitational/teleport/api/types"
apiutils "github.com/gravitational/teleport/api/utils"
"github.com/gravitational/teleport/lib/backend"
"github.com/gravitational/teleport/lib/utils"
)
// Provisioner governs adding new nodes to the cluster
type Provisioner interface {
// UpsertToken adds provisioning tokens for the auth server
UpsertToken(ctx context.Context, token types.ProvisionToken) error
// CreateToken adds provisioning tokens for the auth server
CreateToken(ctx context.Context, token types.ProvisionToken) error
// GetToken finds and returns token by id
GetToken(ctx context.Context, token string) (types.ProvisionToken, error)
// DeleteToken deletes provisioning token
// Imlementations must guarantee that this returns trace.NotFound error if the token doesn't exist
DeleteToken(ctx context.Context, token string) error
// GetTokens returns all non-expired tokens
// Deprecated: use [ListProvisionTokens] instead.
// TODO(hugoShaka): DELETE IN 19.0.0
GetTokens(ctx context.Context) ([]types.ProvisionToken, error)
// PatchToken performs a conditional update on the named token using
// `updateFn`, retrying internally if a comparison failure occurs.
PatchToken(
ctx context.Context,
token string,
updateFn func(types.ProvisionToken) (types.ProvisionToken, error),
) (types.ProvisionToken, error)
// ListProvisionTokens retrieves a paginated list of provision tokens.
ListProvisionTokens(ctx context.Context, pageSize int, pageToken string, anyRoles types.SystemRoles, botName string) ([]types.ProvisionToken, string, error)
}
// ProvisionerInternal extends the Provisioner interface with auth-specific internal methods.
type ProvisionerInternal interface {
Provisioner
// AppendPutProvisionTokenActions adds conditional actions to an atomic write
// to create or update a provision token.
AppendPutProvisionTokenActions(
actions []backend.ConditionalAction,
token types.ProvisionToken,
condition backend.Condition,
) ([]backend.ConditionalAction, error)
// AppendDeleteProvisionTokenActions adds conditional actions to an atomic
// write to delete a provision token.
AppendDeleteProvisionTokenActions(
actions []backend.ConditionalAction,
token string,
condition backend.Condition,
) ([]backend.ConditionalAction, error)
}
// MustCreateProvisionToken returns a new valid provision token
// or panics, used in tests
func MustCreateProvisionToken(token string, roles types.SystemRoles, expires time.Time) types.ProvisionToken {
t, err := types.NewProvisionToken(token, roles, expires)
if err != nil {
panic(err)
}
return t
}
// UnmarshalProvisionToken unmarshals the ProvisionToken resource from JSON.
func UnmarshalProvisionToken(data []byte, opts ...MarshalOption) (types.ProvisionToken, error) {
if len(data) == 0 {
return nil, trace.BadParameter("missing provision token data")
}
cfg, err := CollectOptions(opts)
if err != nil {
return nil, trace.Wrap(err)
}
var h types.ResourceHeader
err = utils.FastUnmarshal(data, &h)
if err != nil {
return nil, trace.Wrap(err)
}
switch h.Version {
case "":
var p types.ProvisionTokenV1
err := utils.FastUnmarshal(data, &p)
if err != nil {
return nil, trace.Wrap(err)
}
v2 := p.V2()
if cfg.Revision != "" {
v2.SetRevision(cfg.Revision)
}
return v2, nil
case types.V2:
var p types.ProvisionTokenV2
if err := utils.FastUnmarshal(data, &p); err != nil {
return nil, trace.BadParameter("%s", err)
}
if err := p.CheckAndSetDefaults(); err != nil {
return nil, trace.Wrap(err)
}
if cfg.Revision != "" {
p.SetRevision(cfg.Revision)
}
return &p, nil
}
return nil, trace.BadParameter("server resource version %v is not supported", h.Version)
}
// strongValidateProvisionTokenWithDefaults checks if the provision token is valid and sets defaults if necessary..
func strongValidateProvisionTokenWithDefaults(token *types.ProvisionTokenV2) error {
if err := token.CheckAndSetDefaults(); err != nil {
return trace.Wrap(err)
}
// for now there are no additional, on-write validations for token types other than kubernetes
if token.GetJoinMethod() != types.JoinMethodKubernetes {
return nil
}
kube := token.GetKubernetes()
if kube == nil {
// technically should never happen since CheckAndSetDefaults() performs a similar check,
// but we'll be defensive just in case
return trace.BadParameter("allow: at least one rule must be set")
}
for i, rule := range kube.Allow {
// validation for empty namespace and account was added much later than the rest of the validations
// in CheckAndSetDefaults(), so we only enforce them when marshaling a token rather than when unmarshaling
namespace, account, _ := strings.Cut(rule.ServiceAccount, ":")
if namespace == "" || account == "" {
return trace.BadParameter(
`allow[%d].service_account: name of service account should be in format "namespace:service_account", got %q instead`,
i,
rule.ServiceAccount,
)
}
}
return nil
}
// MarshalProvisionToken marshals the ProvisionToken resource to JSON.
func MarshalProvisionToken(provisionToken types.ProvisionToken, opts ...MarshalOption) ([]byte, error) {
cfg, err := CollectOptions(opts)
if err != nil {
return nil, trace.Wrap(err)
}
switch provisionToken := provisionToken.(type) {
case *types.ProvisionTokenV2:
if err := strongValidateProvisionTokenWithDefaults(provisionToken); err != nil {
return nil, trace.Wrap(err)
}
provisionToken = maybeResetProtoRevision(cfg.PreserveRevision, provisionToken)
if cfg.GetVersion() == types.V1 {
return utils.FastMarshal(provisionToken.V1())
}
return utils.FastMarshal(provisionToken)
default:
return nil, trace.BadParameter("unrecognized provision token version %T", provisionToken)
}
}
// CloneProvisionToken returns a deep copy of the given provision token, per
// `apiutils.CloneProtoMsg()`. Fields in the clone may be modified without
// affecting the original. Only V2 is supported.
func CloneProvisionToken(provisionToken types.ProvisionToken) (types.ProvisionToken, error) {
switch provisionToken := provisionToken.(type) {
case *types.ProvisionTokenV2:
clone := apiutils.CloneProtoMsg(provisionToken)
return clone, nil
default:
return nil, trace.BadParameter("cannot clone unsupported provision token version %T", provisionToken)
}
}