Files
teleport/lib/modules/modules.go
T

493 lines
19 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 modules allows external packages override certain behavioral
// aspects of teleport
package modules
import (
"context"
"crypto"
"errors"
"fmt"
"os"
"runtime"
"strconv"
"sync"
"time"
"github.com/gravitational/trace"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/types/accesslist"
"github.com/gravitational/teleport/api/utils/keys"
"github.com/gravitational/teleport/api/utils/keys/hardwarekey"
"github.com/gravitational/teleport/entitlements"
"github.com/gravitational/teleport/lib/automaticupgrades"
"github.com/gravitational/teleport/lib/tlsca"
)
// Features provides supported and unsupported features
type Features struct {
// --------------- Cloud Settings
// Cloud enables some cloud-related features
Cloud bool
// CustomTheme holds the name of WebUI custom theme.
CustomTheme string
// IsStripeManaged indicates if the cluster billing is managed via Stripe
IsStripeManaged bool
// IsUsageBasedBilling enables some usage-based billing features
IsUsageBasedBilling bool
// Questionnaire indicates whether cluster users should get an onboarding questionnaire
Questionnaire bool
// SupportType indicates the type of customer's support
SupportType proto.SupportType
// Entitlements reflect Cloud Entitlements including access and limits
Entitlements map[entitlements.EntitlementKind]EntitlementInfo
// CloudAnonymizationKey is the key used to anonymize usage events in a cluster.
// Only applicable for Cloud customers (self-hosted clusters get their anonymization key from the
// license file).
CloudAnonymizationKey []byte
// todo (michellescripts) have the following fields evaluated for deprecation, consolidation, or fetch from Cloud
// AdvancedAccessWorkflows is currently set to the value of the Cloud Access Requests entitlement
AdvancedAccessWorkflows bool
// RecoveryCodes enables account recovery codes
RecoveryCodes bool
// Plugins enables hosted plugins
Plugins bool
// AutomaticUpgrades enables automatic upgrades of agents/services.
AutomaticUpgrades bool
// AccessGraph enables the usage of access graph.
// NOTE: this is a legacy flag that is currently used to signal
// that Access Graph integration is *enabled* on a cluster.
// *Access* to the feature is gated on the `Policy` flag.
// TODO(justinas): remove this field once "TAG enabled" status is moved to a resource in the backend.
AccessGraph bool
// AccessMonitoringConfigured contributes to the enablement of access monitoring.
// NOTE: this flag is used to signal that Access Monitoring is *enabled* on a cluster.
// *Access* to the feature is gated on the `AccessMonitoring` entitlement.
AccessMonitoringConfigured bool
// --------------- Deprecated Fields
// AccessControls enables FIPS access controls
// Deprecated
AccessControls bool
// Assist enables Assistant feature
// Deprecated
Assist bool
// ProductType describes the product being used.
// Deprecated
ProductType ProductType
}
// EntitlementInfo is the state and limits of a particular entitlement
type EntitlementInfo struct {
// Enabled indicates the feature is 'on' if true; feature is disabled if false
Enabled bool
// Limit indicates the allotted amount of use when limited; if 0 use is unlimited
Limit int32
}
// UnderLimit tests that a given entitlement is under its prescribed limit
// based on the supplied use count. A return value of `true` indicates that
// there is still at least *some* capacity left in the entitlement. The actual
// definition of a "use" depends on the entitlement in question.
// A disabled entitlement is always out of its limit.
func (e EntitlementInfo) UnderLimit(count int) bool {
return e.Enabled && (e.Limit == 0 || count < int(e.Limit))
}
// ToProto converts Features into proto.Features
func (f Features) ToProto() *proto.Features {
protoF := &proto.Features{
Cloud: f.Cloud,
CustomTheme: f.CustomTheme,
IsStripeManaged: f.IsStripeManaged,
IsUsageBased: f.IsUsageBasedBilling,
Questionnaire: f.Questionnaire,
SupportType: f.SupportType,
AccessControls: f.AccessControls,
AccessGraph: f.AccessGraph,
AdvancedAccessWorkflows: f.AdvancedAccessWorkflows,
AutomaticUpgrades: f.AutomaticUpgrades,
Plugins: f.Plugins,
ProductType: proto.ProductType(f.ProductType),
RecoveryCodes: f.RecoveryCodes,
AccessMonitoringConfigured: f.AccessMonitoringConfigured,
Entitlements: f.EntitlementsToProto(),
}
// remove setLegacyLogic in v18
setLegacyLogic(protoF, f)
return protoF
}
// setLegacyLogic sets the deprecated fields; to be removed in v18 - use entitlements
func setLegacyLogic(protoF *proto.Features, f Features) {
protoF.Kubernetes = f.GetEntitlement(entitlements.K8s).Enabled
protoF.App = f.GetEntitlement(entitlements.App).Enabled
protoF.DB = f.GetEntitlement(entitlements.DB).Enabled
protoF.OIDC = f.GetEntitlement(entitlements.OIDC).Enabled
protoF.SAML = f.GetEntitlement(entitlements.SAML).Enabled
protoF.HSM = f.GetEntitlement(entitlements.HSM).Enabled
protoF.Desktop = f.GetEntitlement(entitlements.Desktop).Enabled
protoF.FeatureHiding = f.GetEntitlement(entitlements.FeatureHiding).Enabled
protoF.IdentityGovernance = f.GetEntitlement(entitlements.Identity).Enabled
protoF.ExternalAuditStorage = f.GetEntitlement(entitlements.ExternalAuditStorage).Enabled
protoF.JoinActiveSessions = f.GetEntitlement(entitlements.JoinActiveSessions).Enabled
protoF.MobileDeviceManagement = f.GetEntitlement(entitlements.MobileDeviceManagement).Enabled
protoF.DeviceTrust = &proto.DeviceTrustFeature{
Enabled: f.GetEntitlement(entitlements.DeviceTrust).Enabled, DevicesUsageLimit: f.GetEntitlement(entitlements.DeviceTrust).Limit,
}
protoF.AccessRequests = &proto.AccessRequestsFeature{
MonthlyRequestLimit: f.GetEntitlement(entitlements.AccessRequests).Limit,
}
protoF.AccessMonitoring = &proto.AccessMonitoringFeature{
Enabled: f.AccessMonitoringConfigured, MaxReportRangeLimit: f.GetEntitlement(entitlements.AccessMonitoring).Limit,
}
protoF.AccessList = &proto.AccessListFeature{
CreateLimit: f.GetEntitlement(entitlements.AccessLists).Limit,
}
protoF.Policy = &proto.PolicyFeature{
Enabled: f.GetEntitlement(entitlements.Policy).Enabled,
}
}
// EntitlementsToProto takes the features.Entitlements object and returns a proto version. If not present on Features, the
// proto entitlement will default to false
func (f Features) EntitlementsToProto() map[string]*proto.EntitlementInfo {
all := entitlements.AllEntitlements
result := make(map[string]*proto.EntitlementInfo, len(all))
for _, e := range all {
al, ok := f.Entitlements[e]
if !ok {
result[string(e)] = &proto.EntitlementInfo{}
continue
}
result[string(e)] = &proto.EntitlementInfo{
Enabled: al.Enabled,
Limit: al.Limit,
}
}
return result
}
// GetEntitlement takes an entitlement and returns either the Features entitlement, or if not present, a false entitlement
func (f Features) GetEntitlement(e entitlements.EntitlementKind) EntitlementInfo {
al, ok := f.Entitlements[e]
if !ok {
return EntitlementInfo{}
}
return EntitlementInfo{
Enabled: al.Enabled,
Limit: al.Limit,
}
}
// GetProtoEntitlement takes a proto features set and an entitlement and returns either the proto features entitlement,
// or if not present, a false entitlement
func GetProtoEntitlement(f *proto.Features, e entitlements.EntitlementKind) *proto.EntitlementInfo {
fE := f.GetEntitlements()
al, ok := fE[string(e)]
if !ok {
return &proto.EntitlementInfo{}
}
return &proto.EntitlementInfo{
Enabled: al.Enabled,
Limit: al.Limit,
}
}
// ProductType is the type of product.
type ProductType int32
const (
ProductTypeUnknown ProductType = 0
// ProductTypeTeam is Teleport ProductTypeTeam product.
ProductTypeTeam ProductType = 1
// ProductTypeEUB is Teleport Enterprise Usage Based product.
ProductTypeEUB ProductType = 2
)
// AccessResourcesGetter is a minimal interface that is used to get access lists
// and related resources from the backend.
type AccessResourcesGetter interface {
ListAccessLists(context.Context, int, string) ([]*accesslist.AccessList, string, error)
ListResources(ctx context.Context, req proto.ListResourcesRequest) (*types.ListResourcesResponse, error)
GetAccessList(context.Context, string) (*accesslist.AccessList, error)
GetAccessLists(ctx context.Context) ([]*accesslist.AccessList, error)
ListAccessListMembers(ctx context.Context, accessList string, pageSize int, pageToken string) (members []*accesslist.AccessListMember, nextToken string, err error)
GetAccessListMember(ctx context.Context, accessList string, memberName string) (*accesslist.AccessListMember, error)
GetUser(ctx context.Context, userName string, withSecrets bool) (types.User, error)
GetRole(ctx context.Context, name string) (types.Role, error)
GetLock(ctx context.Context, name string) (types.Lock, error)
GetLocks(ctx context.Context, inForceOnly bool, targets ...types.LockTarget) ([]types.Lock, error)
}
type AccessListSuggestionClient interface {
GetUser(ctx context.Context, userName string, withSecrets bool) (types.User, error)
RoleGetter
GetAccessRequestAllowedPromotions(ctx context.Context, req types.AccessRequest) (*types.AccessRequestAllowedPromotions, error)
GetAccessRequests(ctx context.Context, filter types.AccessRequestFilter) ([]types.AccessRequest, error)
}
type RoleGetter interface {
GetRole(ctx context.Context, name string) (types.Role, error)
}
type AccessListAndMembersGetter interface {
GetAccessList(ctx context.Context, name string) (*accesslist.AccessList, error)
GetAccessLists(ctx context.Context) ([]*accesslist.AccessList, error)
GetAccessListMember(ctx context.Context, accessList string, memberName string) (*accesslist.AccessListMember, error)
ListAccessListMembers(ctx context.Context, accessListName string, pageSize int, pageToken string) (members []*accesslist.AccessListMember, nextToken string, err error)
}
// Modules defines interface that external libraries can implement customizing
// default teleport behavior
type Modules interface {
// PrintVersion prints teleport version
PrintVersion()
// IsBoringBinary checks if the binary was compiled with BoringCrypto.
IsBoringBinary() bool
// Features returns supported features
Features() Features
// SetFeatures set features queried from Cloud
SetFeatures(Features)
// BuildType returns build type (OSS, Community or Enterprise)
BuildType() string
// IsEnterpriseBuild returns if the binary was built with enterprise modules
IsEnterpriseBuild() bool
// IsOSSBuild returns if the binary was built without enterprise modules
IsOSSBuild() bool
// AttestHardwareKey attests a hardware key and returns its associated private key policy.
AttestHardwareKey(context.Context, interface{}, *hardwarekey.AttestationStatement, crypto.PublicKey, time.Duration) (*keys.AttestationData, error)
// GenerateAccessRequestPromotions generates a list of valid promotions for given access request.
GenerateAccessRequestPromotions(context.Context, AccessResourcesGetter, types.AccessRequest) (*types.AccessRequestAllowedPromotions, error)
// GetSuggestedAccessLists generates a list of valid promotions for given access request.
GetSuggestedAccessLists(ctx context.Context, identity *tlsca.Identity, clt AccessListSuggestionClient, accessListGetter AccessListAndMembersGetter, requestID string) ([]*accesslist.AccessList, error)
// EnableRecoveryCodes enables the usage of recovery codes for resetting forgotten passwords
EnableRecoveryCodes()
// EnablePlugins enables the hosted plugins runtime
EnablePlugins()
// EnableAccessGraph enables the usage of access graph.
EnableAccessGraph()
// EnableAccessMonitoring enables the usage of access monitoring.
EnableAccessMonitoring()
// LicenseExpiry returns the expiry date of the enterprise license, if applicable.
LicenseExpiry() time.Time
}
const (
// BuildOSS specifies open source build type
BuildOSS = "oss"
// BuildEnterprise specifies enterprise build type
BuildEnterprise = "ent"
// BuildCommunity identifies builds of Teleport Community Edition,
// which are distributed on goteleport.com/download under our
// Teleport Community license agreement.
BuildCommunity = "community"
)
// SetModules sets the modules interface
func SetModules(m Modules) {
mutex.Lock()
defer mutex.Unlock()
modules = m
}
// GetModules returns the modules interface
func GetModules() Modules {
mutex.Lock()
defer mutex.Unlock()
return modules
}
var ErrCannotDisableSecondFactor = errors.New("cannot disable multi-factor authentication")
// ValidateResource performs additional resource checks.
func ValidateResource(res types.Resource) error {
// todo(tross): DELETE WHEN ABLE TO [remove env var, leave insecure test mode]
allowNoSecondFactor, _ := strconv.ParseBool(os.Getenv(teleport.EnvVarAllowNoSecondFactor))
if GetModules().Features().Cloud ||
(!allowNoSecondFactor && !IsInsecureTestMode()) {
switch r := res.(type) {
case types.AuthPreference:
if !r.IsSecondFactorEnforced() {
return trace.Wrap(ErrCannotDisableSecondFactor)
}
}
}
// All checks below are Cloud-specific.
if !GetModules().Features().Cloud {
return nil
}
switch r := res.(type) {
case types.SessionRecordingConfig:
switch r.GetMode() {
case types.RecordAtProxy, types.RecordAtProxySync:
return trace.BadParameter("cannot set proxy recording mode on Cloud")
}
if !r.GetProxyChecksHostKeys() {
return trace.BadParameter("cannot disable strict host key checking on Cloud")
}
}
return nil
}
type defaultModules struct {
automaticUpgrades bool
loadDynamicValues sync.Once
}
var teleportBuildType = BuildOSS
// BuildType returns build type (OSS, Community or Enterprise)
func (p *defaultModules) BuildType() string {
return teleportBuildType
}
// IsEnterpriseBuild returns false for [defaultModules].
func (p *defaultModules) IsEnterpriseBuild() bool {
return false
}
// IsOSSBuild returns true for [defaultModules].
func (p *defaultModules) IsOSSBuild() bool {
return true
}
// PrintVersion prints the Teleport version.
func (p *defaultModules) PrintVersion() {
fmt.Printf("Teleport v%s git:%s %s\n", teleport.Version, teleport.Gitref, runtime.Version())
}
// LicenseExpiry returns the expiry date of the enterprise license, if applicable.
// Returns the zero value for time.Time for OSS.
func (p *defaultModules) LicenseExpiry() time.Time {
return time.Time{}
}
// Features returns supported features for default modules which is applied for OSS users
// todo (michellescripts) remove deprecated features
func (p *defaultModules) Features() Features {
p.loadDynamicValues.Do(func() {
p.automaticUpgrades = automaticupgrades.IsEnabled()
})
return Features{
AutomaticUpgrades: p.automaticUpgrades,
SupportType: proto.SupportType_SUPPORT_TYPE_FREE,
Entitlements: map[entitlements.EntitlementKind]EntitlementInfo{
entitlements.App: {Enabled: true, Limit: 0},
entitlements.DB: {Enabled: true, Limit: 0},
entitlements.Desktop: {Enabled: true, Limit: 0},
entitlements.JoinActiveSessions: {Enabled: true, Limit: 0},
entitlements.K8s: {Enabled: true, Limit: 0},
},
}
}
// SetFeatures sets features queried from Cloud.
// This is a noop since OSS teleport does not support enterprise features
func (p *defaultModules) SetFeatures(f Features) {
}
func (p *defaultModules) IsBoringBinary() bool {
return IsBoringBinary()
}
// AttestHardwareKey attests a hardware key.
func (p *defaultModules) AttestHardwareKey(_ context.Context, _ interface{}, _ *hardwarekey.AttestationStatement, _ crypto.PublicKey, _ time.Duration) (*keys.AttestationData, error) {
// Default modules do not support attesting hardware keys.
return nil, trace.NotFound("no attestation data for the given key")
}
// GenerateAccessRequestPromotions is a noop since OSS teleport does not support generating access list promotions.
func (p *defaultModules) GenerateAccessRequestPromotions(_ context.Context, _ AccessResourcesGetter, _ types.AccessRequest) (*types.AccessRequestAllowedPromotions, error) {
// The default module does not support generating access list promotions.
return types.NewAccessRequestAllowedPromotions(nil), nil
}
func (p *defaultModules) GetSuggestedAccessLists(ctx context.Context, identity *tlsca.Identity, clt AccessListSuggestionClient,
accessListGetter AccessListAndMembersGetter, requestID string,
) ([]*accesslist.AccessList, error) {
return nil, trace.NotImplemented("GetSuggestedAccessLists not implemented")
}
// EnableRecoveryCodes enables recovery codes. This is a noop since OSS teleport does not
// support recovery codes
func (p *defaultModules) EnableRecoveryCodes() {
}
// EnablePlugins enables hosted plugins runtime.
// This is a noop since OSS teleport does not support hosted plugins
func (p *defaultModules) EnablePlugins() {
}
// EnableAccessGraph enables the usage of access graph.
// This is a noop since OSS teleport does not support access graph.
func (p *defaultModules) EnableAccessGraph() {}
// EnableAccessMonitoring enables the usage of access monitoring.
// This is a noop since OSS teleport does not support access monitoring.
func (p *defaultModules) EnableAccessMonitoring() {}
var (
mutex sync.Mutex
modules Modules = &defaultModules{}
)
var (
// flagLock protects access to accessing insecure test mode below
flagLock sync.Mutex
// insecureTestAllow is used to allow disabling second factor auth
// in test environments. Not user configurable.
insecureTestAllowNoSecondFactor bool
)
// SetInsecureTestMode is used to set insecure test mode on, to allow
// second factor to be disabled
func SetInsecureTestMode(m bool) {
flagLock.Lock()
defer flagLock.Unlock()
insecureTestAllowNoSecondFactor = m
}
// IsInsecureTestMode retrieves the current insecure test mode value
func IsInsecureTestMode() bool {
flagLock.Lock()
defer flagLock.Unlock()
return insecureTestAllowNoSecondFactor
}