mirror of
https://github.com/gravitational/teleport
synced 2024-10-20 01:03:40 +00:00
f79906ba2e
* Refactor database DiscoveryResourceChecker * fix race
165 lines
5.5 KiB
Go
165 lines
5.5 KiB
Go
/*
|
|
Copyright 2023 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 cloud
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
|
|
"github.com/gravitational/trace"
|
|
"github.com/sirupsen/logrus"
|
|
"golang.org/x/exp/slices"
|
|
|
|
"github.com/gravitational/teleport/api/types"
|
|
"github.com/gravitational/teleport/lib/cloud"
|
|
"github.com/gravitational/teleport/lib/cloud/aws"
|
|
"github.com/gravitational/teleport/lib/services"
|
|
"github.com/gravitational/teleport/lib/utils"
|
|
)
|
|
|
|
// credentialsChecker performs some quick checks to see whether this database
|
|
// agent can handle the incoming database wrt to the agent's credentials.
|
|
//
|
|
// Note that this checker warns the user with suggestions on how to configure
|
|
// the credentials correctly instead of returning errors.
|
|
type credentialsChecker struct {
|
|
cloudClients cloud.Clients
|
|
resourceMatchers []services.ResourceMatcher
|
|
log logrus.FieldLogger
|
|
cache *utils.FnCache
|
|
}
|
|
|
|
func newCrednentialsChecker(cfg DiscoveryResourceCheckerConfig) (*credentialsChecker, error) {
|
|
cache, err := utils.NewFnCache(utils.FnCacheConfig{
|
|
TTL: 10 * time.Minute,
|
|
Context: cfg.Context,
|
|
})
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
return &credentialsChecker{
|
|
cloudClients: cfg.Clients,
|
|
resourceMatchers: cfg.ResourceMatchers,
|
|
log: cfg.Log,
|
|
cache: cache,
|
|
}, nil
|
|
}
|
|
|
|
// Check performs some quick checks to see whether this database agent can
|
|
// handle the incoming database wrt to the agent's credentials.
|
|
func (c *credentialsChecker) Check(ctx context.Context, database types.Database) error {
|
|
switch {
|
|
case database.IsAWSHosted():
|
|
c.checkAWS(ctx, database)
|
|
case database.IsAzure():
|
|
c.checkAzure(ctx, database)
|
|
default:
|
|
c.log.Debugf("Database %q has unknown cloud type %q.", database.GetName(), database.GetType())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *credentialsChecker) checkAWS(ctx context.Context, database types.Database) {
|
|
meta := database.GetAWS()
|
|
identity, err := c.getAWSIdentity(ctx, &meta)
|
|
if err != nil {
|
|
c.warn(err, database, "Failed to get AWS identity when checking a database created by the discovery service.")
|
|
return
|
|
}
|
|
|
|
if meta.AccountID != "" && meta.AccountID != identity.GetAccountID() {
|
|
c.warn(nil, database, fmt.Sprintf("The database agent's identity and discovered database %q have different AWS account IDs (%s vs %s).",
|
|
database.GetName(),
|
|
identity.GetAccountID(),
|
|
meta.AccountID,
|
|
))
|
|
return
|
|
}
|
|
}
|
|
|
|
// getAWSIdentity returns the identity used to access the given database,
|
|
// that is either the agent's identity or the database's configured assume-role.
|
|
func (c *credentialsChecker) getAWSIdentity(ctx context.Context, meta *types.AWS) (aws.Identity, error) {
|
|
if meta.AssumeRoleARN != "" {
|
|
// If the database has an assume role ARN, use that instead of
|
|
// agent identity. This avoids an unnecessary sts call too.
|
|
return aws.IdentityFromArn(meta.AssumeRoleARN)
|
|
}
|
|
|
|
identity, err := utils.FnCacheGet(ctx, c.cache, types.CloudAWS, func(ctx context.Context) (aws.Identity, error) {
|
|
client, err := c.cloudClients.GetAWSSTSClient(ctx, "")
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return aws.GetIdentityWithClient(ctx, client)
|
|
})
|
|
return identity, trace.Wrap(err)
|
|
}
|
|
|
|
func (c *credentialsChecker) checkAzure(ctx context.Context, database types.Database) {
|
|
allSubIDs, err := utils.FnCacheGet(ctx, c.cache, types.CloudAzure, func(ctx context.Context) ([]string, error) {
|
|
client, err := c.cloudClients.GetAzureSubscriptionClient()
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return client.ListSubscriptionIDs(ctx)
|
|
})
|
|
if err != nil {
|
|
c.warn(err, database, "Failed to get Azure subscription IDs when checking a database created by the discovery service.")
|
|
return
|
|
}
|
|
|
|
rid, err := arm.ParseResourceID(database.GetAzure().ResourceID)
|
|
if err != nil {
|
|
c.log.Warnf("Failed to parse resource ID of database %q: %v.", database.GetName(), err)
|
|
return
|
|
}
|
|
|
|
if !slices.Contains(allSubIDs, rid.SubscriptionID) {
|
|
c.warn(nil, database, fmt.Sprintf("The discovered database %q is in a subscription (ID: %s) that the database agent does not have access to.",
|
|
database.GetName(),
|
|
rid.SubscriptionID,
|
|
))
|
|
return
|
|
}
|
|
}
|
|
|
|
func (c *credentialsChecker) warn(err error, database types.Database, msg string) {
|
|
log := c.log.WithField("database", database)
|
|
if err != nil {
|
|
log = log.WithField("error", err.Error())
|
|
}
|
|
|
|
logLevel := logrus.InfoLevel
|
|
if c.isWildcardMatcher() {
|
|
logLevel = logrus.WarnLevel
|
|
}
|
|
log.Logf(logLevel, "%s You can update \"db_service.resources\" section of this agent's config file to filter out unwanted resources (see https://goteleport.com/docs/database-access/reference/configuration/ for more details). If this database is intended to be handled by this agent, please verify that valid cloud credentials are configured for the agent.", msg)
|
|
}
|
|
|
|
func (c *credentialsChecker) isWildcardMatcher() bool {
|
|
if len(c.resourceMatchers) != 1 {
|
|
return false
|
|
}
|
|
|
|
wildcardLabels := c.resourceMatchers[0].Labels[types.Wildcard]
|
|
return len(wildcardLabels) == 1 && wildcardLabels[0] == types.Wildcard
|
|
}
|