Amazon DocumentDB access part 1 (#43867)

* Amazon DocumentDB access part 1

* fix lint

* make derive
This commit is contained in:
STeve (Xin) Huang
2024-07-16 15:17:06 +00:00
committed by GitHub
parent b291e27820
commit f240bfb312
22 changed files with 3194 additions and 2368 deletions
@@ -466,6 +466,12 @@ message AWS {
IAMPolicyStatus IAMPolicyStatus = 14 [(gogoproto.jsontag) = "iam_policy_status"];
// SessionTags is a list of AWS STS session tags.
map<string, string> SessionTags = 15 [(gogoproto.jsontag) = "session_tags,omitempty"];
// DocumentDB contains AWS DocumentDB specific metadata.
DocumentDB DocumentDB = 16 [
(gogoproto.nullable) = false,
(gogoproto.jsontag) = "docdb,omitempty"
];
}
// SecretStore contains secret store configurations.
@@ -552,6 +558,16 @@ message OpenSearch {
string EndpointType = 3 [(gogoproto.jsontag) = "endpoint_type,omitempty"];
}
// DocumentDB contains AWS DocumentDB specific metadata.
message DocumentDB {
// ClusterID is the cluster identifier.
string ClusterID = 1 [(gogoproto.jsontag) = "cluster_id,omitempty"];
// InstanceID is the instance identifier.
string InstanceID = 2 [(gogoproto.jsontag) = "instance_id,omitempty"];
// EndpointType is the type of the endpoint.
string EndpointType = 3 [(gogoproto.jsontag) = "endpoint_type,omitempty"];
}
// GCPCloudSQL contains parameters specific to GCP Cloud SQL databases.
message GCPCloudSQL {
// ProjectID is the GCP project ID the Cloud SQL instance resides in.
+26 -1
View File
@@ -584,6 +584,9 @@ func (d *DatabaseV3) getAWSType() (string, bool) {
if aws.RDSProxy.Name != "" || aws.RDSProxy.CustomEndpointName != "" {
return DatabaseTypeRDSProxy, true
}
if aws.DocumentDB.ClusterID != "" || aws.DocumentDB.InstanceID != "" {
return DatabaseTypeDocumentDB, true
}
if aws.Region != "" || aws.RDS.InstanceID != "" || aws.RDS.ResourceID != "" || aws.RDS.ClusterID != "" {
return DatabaseTypeRDS, true
}
@@ -811,6 +814,23 @@ func (d *DatabaseV3) CheckAndSetDefaults() error {
d.Spec.AWS.MemoryDB.TLSEnabled = endpointInfo.TransitEncryptionEnabled
d.Spec.AWS.MemoryDB.EndpointType = endpointInfo.EndpointType
case awsutils.IsDocumentDBEndpoint(d.Spec.URI):
endpointInfo, err := awsutils.ParseDocumentDBEndpoint(d.Spec.URI)
if err != nil {
slog.WarnContext(context.Background(), "Failed to parse DocumentDB endpoint.", "uri", d.Spec.URI, "error", err)
break
}
if d.Spec.AWS.DocumentDB.ClusterID == "" {
d.Spec.AWS.DocumentDB.ClusterID = endpointInfo.ClusterID
}
if d.Spec.AWS.DocumentDB.InstanceID == "" {
d.Spec.AWS.DocumentDB.InstanceID = endpointInfo.InstanceID
}
if d.Spec.AWS.Region == "" {
d.Spec.AWS.Region = endpointInfo.Region
}
d.Spec.AWS.DocumentDB.EndpointType = endpointInfo.EndpointType
case azureutils.IsDatabaseEndpoint(d.Spec.URI):
// For Azure MySQL and PostgresSQL.
name, err := azureutils.ParseDatabaseEndpoint(d.Spec.URI)
@@ -1046,7 +1066,8 @@ func (d *DatabaseV3) RequireAWSIAMRolesAsUsers() bool {
case DatabaseTypeAWSKeyspaces,
DatabaseTypeDynamoDB,
DatabaseTypeOpenSearch,
DatabaseTypeRedshiftServerless:
DatabaseTypeRedshiftServerless,
DatabaseTypeDocumentDB:
return true
default:
return false
@@ -1093,6 +1114,8 @@ func (d *DatabaseV3) GetEndpointType() string {
if details, err := awsutils.ParseRDSEndpoint(d.GetURI()); err == nil {
return details.EndpointType
}
case DatabaseTypeDocumentDB:
return d.GetAWS().DocumentDB.EndpointType
}
return ""
}
@@ -1149,6 +1172,8 @@ const (
DatabaseTypeOpenSearch = "opensearch"
// DatabaseTypeMongoAtlas
DatabaseTypeMongoAtlas = "mongo-atlas"
// DatabaseTypeDocumentDB is the database type for AWS-hosted DocumentDB.
DatabaseTypeDocumentDB = "docdb"
)
// GetServerName returns the GCP database project and instance as "<project-id>:<instance-id>".
+109 -99
View File
@@ -34,7 +34,8 @@ func deriveTeleportEqualAWS(this, that *AWS) bool {
this.AssumeRoleARN == that.AssumeRoleARN &&
deriveTeleportEqual_7(&this.OpenSearch, &that.OpenSearch) &&
this.IAMPolicyStatus == that.IAMPolicyStatus &&
deriveTeleportEqual_8(this.SessionTags, that.SessionTags)
deriveTeleportEqual_8(this.SessionTags, that.SessionTags) &&
deriveTeleportEqual_9(&this.DocumentDB, &that.DocumentDB)
}
// deriveTeleportEqualGCPCloudSQL returns whether this and that are equal.
@@ -51,7 +52,7 @@ func deriveTeleportEqualAzure(this, that *Azure) bool {
this != nil && that != nil &&
this.Name == that.Name &&
this.ResourceID == that.ResourceID &&
deriveTeleportEqual_9(&this.Redis, &that.Redis) &&
deriveTeleportEqual_10(&this.Redis, &that.Redis) &&
this.IsFlexiServer == that.IsFlexiServer
}
@@ -63,7 +64,7 @@ func deriveTeleportEqualDatabaseV3(this, that *DatabaseV3) bool {
this.SubKind == that.SubKind &&
this.Version == that.Version &&
deriveTeleportEqualMetadata(&this.Metadata, &that.Metadata) &&
deriveTeleportEqual_10(&this.Spec, &that.Spec)
deriveTeleportEqual_11(&this.Spec, &that.Spec)
}
// deriveTeleportEqualWindowsDesktopV3 returns whether this and that are equal.
@@ -71,7 +72,7 @@ func deriveTeleportEqualWindowsDesktopV3(this, that *WindowsDesktopV3) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
deriveTeleportEqualResourceHeader(&this.ResourceHeader, &that.ResourceHeader) &&
deriveTeleportEqual_11(&this.Spec, &that.Spec)
deriveTeleportEqual_12(&this.Spec, &that.Spec)
}
// deriveTeleportEqualKubeAzure returns whether this and that are equal.
@@ -110,7 +111,7 @@ func deriveTeleportEqualKubernetesClusterV3(this, that *KubernetesClusterV3) boo
this.SubKind == that.SubKind &&
this.Version == that.Version &&
deriveTeleportEqualMetadata(&this.Metadata, &that.Metadata) &&
deriveTeleportEqual_12(&this.Spec, &that.Spec)
deriveTeleportEqual_13(&this.Spec, &that.Spec)
}
// deriveTeleportEqualKubernetesServerV3 returns whether this and that are equal.
@@ -121,7 +122,7 @@ func deriveTeleportEqualKubernetesServerV3(this, that *KubernetesServerV3) bool
this.SubKind == that.SubKind &&
this.Version == that.Version &&
deriveTeleportEqualMetadata(&this.Metadata, &that.Metadata) &&
deriveTeleportEqual_13(&this.Spec, &that.Spec)
deriveTeleportEqual_14(&this.Spec, &that.Spec)
}
// deriveTeleportEqualOktaAssignmentV1 returns whether this and that are equal.
@@ -129,7 +130,7 @@ func deriveTeleportEqualOktaAssignmentV1(this, that *OktaAssignmentV1) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
deriveTeleportEqualResourceHeader(&this.ResourceHeader, &that.ResourceHeader) &&
deriveTeleportEqual_14(&this.Spec, &that.Spec)
deriveTeleportEqual_15(&this.Spec, &that.Spec)
}
// deriveTeleportEqualResourceHeader returns whether this and that are equal.
@@ -158,7 +159,7 @@ func deriveTeleportEqualUserGroupV1(this, that *UserGroupV1) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
deriveTeleportEqualResourceHeader(&this.ResourceHeader, &that.ResourceHeader) &&
deriveTeleportEqual_15(&this.Spec, &that.Spec)
deriveTeleportEqual_16(&this.Spec, &that.Spec)
}
// deriveTeleportEqual returns whether this and that are equal.
@@ -167,12 +168,12 @@ func deriveTeleportEqual(this, that *AppSpecV3) bool {
this != nil && that != nil &&
this.URI == that.URI &&
this.PublicAddr == that.PublicAddr &&
deriveTeleportEqual_16(this.DynamicLabels, that.DynamicLabels) &&
deriveTeleportEqual_17(this.DynamicLabels, that.DynamicLabels) &&
this.InsecureSkipVerify == that.InsecureSkipVerify &&
deriveTeleportEqual_17(this.Rewrite, that.Rewrite) &&
deriveTeleportEqual_18(this.AWS, that.AWS) &&
deriveTeleportEqual_18(this.Rewrite, that.Rewrite) &&
deriveTeleportEqual_19(this.AWS, that.AWS) &&
this.Cloud == that.Cloud &&
deriveTeleportEqual_19(this.UserGroups, that.UserGroups) &&
deriveTeleportEqual_20(this.UserGroups, that.UserGroups) &&
this.Integration == that.Integration
}
@@ -191,7 +192,7 @@ func deriveTeleportEqual_1(this, that *RDS) bool {
this.ClusterID == that.ClusterID &&
this.ResourceID == that.ResourceID &&
this.IAMAuth == that.IAMAuth &&
deriveTeleportEqual_19(this.Subnets, that.Subnets) &&
deriveTeleportEqual_20(this.Subnets, that.Subnets) &&
this.VPCID == that.VPCID
}
@@ -200,7 +201,7 @@ func deriveTeleportEqual_2(this, that *ElastiCache) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
this.ReplicationGroupID == that.ReplicationGroupID &&
deriveTeleportEqual_19(this.UserGroupIDs, that.UserGroupIDs) &&
deriveTeleportEqual_20(this.UserGroupIDs, that.UserGroupIDs) &&
this.TransitEncryptionEnabled == that.TransitEncryptionEnabled &&
this.EndpointType == that.EndpointType
}
@@ -271,86 +272,95 @@ func deriveTeleportEqual_8(this, that map[string]string) bool {
}
// deriveTeleportEqual_9 returns whether this and that are equal.
func deriveTeleportEqual_9(this, that *AzureRedis) bool {
func deriveTeleportEqual_9(this, that *DocumentDB) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
this.ClusterID == that.ClusterID &&
this.InstanceID == that.InstanceID &&
this.EndpointType == that.EndpointType
}
// deriveTeleportEqual_10 returns whether this and that are equal.
func deriveTeleportEqual_10(this, that *AzureRedis) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
this.ClusteringPolicy == that.ClusteringPolicy
}
// deriveTeleportEqual_10 returns whether this and that are equal.
func deriveTeleportEqual_10(this, that *DatabaseSpecV3) bool {
// deriveTeleportEqual_11 returns whether this and that are equal.
func deriveTeleportEqual_11(this, that *DatabaseSpecV3) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
this.Protocol == that.Protocol &&
this.URI == that.URI &&
this.CACert == that.CACert &&
deriveTeleportEqual_16(this.DynamicLabels, that.DynamicLabels) &&
deriveTeleportEqual_17(this.DynamicLabels, that.DynamicLabels) &&
deriveTeleportEqualAWS(&this.AWS, &that.AWS) &&
deriveTeleportEqualGCPCloudSQL(&this.GCP, &that.GCP) &&
deriveTeleportEqualAzure(&this.Azure, &that.Azure) &&
deriveTeleportEqual_20(&this.TLS, &that.TLS) &&
deriveTeleportEqual_21(&this.AD, &that.AD) &&
deriveTeleportEqual_22(&this.MySQL, &that.MySQL) &&
deriveTeleportEqual_23(this.AdminUser, that.AdminUser) &&
deriveTeleportEqual_24(&this.MongoAtlas, &that.MongoAtlas) &&
deriveTeleportEqual_25(&this.Oracle, &that.Oracle)
deriveTeleportEqual_21(&this.TLS, &that.TLS) &&
deriveTeleportEqual_22(&this.AD, &that.AD) &&
deriveTeleportEqual_23(&this.MySQL, &that.MySQL) &&
deriveTeleportEqual_24(this.AdminUser, that.AdminUser) &&
deriveTeleportEqual_25(&this.MongoAtlas, &that.MongoAtlas) &&
deriveTeleportEqual_26(&this.Oracle, &that.Oracle)
}
// deriveTeleportEqual_11 returns whether this and that are equal.
func deriveTeleportEqual_11(this, that *WindowsDesktopSpecV3) bool {
// deriveTeleportEqual_12 returns whether this and that are equal.
func deriveTeleportEqual_12(this, that *WindowsDesktopSpecV3) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
this.Addr == that.Addr &&
this.Domain == that.Domain &&
this.HostID == that.HostID &&
this.NonAD == that.NonAD &&
deriveTeleportEqual_26(this.ScreenSize, that.ScreenSize)
deriveTeleportEqual_27(this.ScreenSize, that.ScreenSize)
}
// deriveTeleportEqual_12 returns whether this and that are equal.
func deriveTeleportEqual_12(this, that *KubernetesClusterSpecV3) bool {
// deriveTeleportEqual_13 returns whether this and that are equal.
func deriveTeleportEqual_13(this, that *KubernetesClusterSpecV3) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
deriveTeleportEqual_16(this.DynamicLabels, that.DynamicLabels) &&
deriveTeleportEqual_17(this.DynamicLabels, that.DynamicLabels) &&
bytes.Equal(this.Kubeconfig, that.Kubeconfig) &&
deriveTeleportEqualKubeAzure(&this.Azure, &that.Azure) &&
deriveTeleportEqualKubeAWS(&this.AWS, &that.AWS) &&
deriveTeleportEqualKubeGCP(&this.GCP, &that.GCP)
}
// deriveTeleportEqual_13 returns whether this and that are equal.
func deriveTeleportEqual_13(this, that *KubernetesServerSpecV3) bool {
// deriveTeleportEqual_14 returns whether this and that are equal.
func deriveTeleportEqual_14(this, that *KubernetesServerSpecV3) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
this.Version == that.Version &&
this.Hostname == that.Hostname &&
this.HostID == that.HostID &&
deriveTeleportEqual_27(&this.Rotation, &that.Rotation) &&
deriveTeleportEqual_28(&this.Rotation, &that.Rotation) &&
deriveTeleportEqualKubernetesClusterV3(this.Cluster, that.Cluster) &&
deriveTeleportEqual_19(this.ProxyIDs, that.ProxyIDs)
deriveTeleportEqual_20(this.ProxyIDs, that.ProxyIDs)
}
// deriveTeleportEqual_14 returns whether this and that are equal.
func deriveTeleportEqual_14(this, that *OktaAssignmentSpecV1) bool {
// deriveTeleportEqual_15 returns whether this and that are equal.
func deriveTeleportEqual_15(this, that *OktaAssignmentSpecV1) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
this.User == that.User &&
deriveTeleportEqual_28(this.Targets, that.Targets) &&
deriveTeleportEqual_29(this.Targets, that.Targets) &&
this.CleanupTime.Equal(that.CleanupTime) &&
this.Status == that.Status &&
this.LastTransition.Equal(that.LastTransition) &&
this.Finalized == that.Finalized
}
// deriveTeleportEqual_15 returns whether this and that are equal.
func deriveTeleportEqual_15(this, that *UserGroupSpecV1) bool {
// deriveTeleportEqual_16 returns whether this and that are equal.
func deriveTeleportEqual_16(this, that *UserGroupSpecV1) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
deriveTeleportEqual_19(this.Applications, that.Applications)
deriveTeleportEqual_20(this.Applications, that.Applications)
}
// deriveTeleportEqual_16 returns whether this and that are equal.
func deriveTeleportEqual_16(this, that map[string]CommandLabelV2) bool {
// deriveTeleportEqual_17 returns whether this and that are equal.
func deriveTeleportEqual_17(this, that map[string]CommandLabelV2) bool {
if this == nil || that == nil {
return this == nil && that == nil
}
@@ -362,31 +372,31 @@ func deriveTeleportEqual_16(this, that map[string]CommandLabelV2) bool {
if !ok {
return false
}
if !(deriveTeleportEqual_29(&v, &thatv)) {
if !(deriveTeleportEqual_30(&v, &thatv)) {
return false
}
}
return true
}
// deriveTeleportEqual_17 returns whether this and that are equal.
func deriveTeleportEqual_17(this, that *Rewrite) bool {
// deriveTeleportEqual_18 returns whether this and that are equal.
func deriveTeleportEqual_18(this, that *Rewrite) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
deriveTeleportEqual_19(this.Redirect, that.Redirect) &&
deriveTeleportEqual_30(this.Headers, that.Headers) &&
deriveTeleportEqual_20(this.Redirect, that.Redirect) &&
deriveTeleportEqual_31(this.Headers, that.Headers) &&
this.JWTClaims == that.JWTClaims
}
// deriveTeleportEqual_18 returns whether this and that are equal.
func deriveTeleportEqual_18(this, that *AppAWS) bool {
// deriveTeleportEqual_19 returns whether this and that are equal.
func deriveTeleportEqual_19(this, that *AppAWS) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
this.ExternalID == that.ExternalID
}
// deriveTeleportEqual_19 returns whether this and that are equal.
func deriveTeleportEqual_19(this, that []string) bool {
// deriveTeleportEqual_20 returns whether this and that are equal.
func deriveTeleportEqual_20(this, that []string) bool {
if this == nil || that == nil {
return this == nil && that == nil
}
@@ -401,8 +411,8 @@ func deriveTeleportEqual_19(this, that []string) bool {
return true
}
// deriveTeleportEqual_20 returns whether this and that are equal.
func deriveTeleportEqual_20(this, that *DatabaseTLS) bool {
// deriveTeleportEqual_21 returns whether this and that are equal.
func deriveTeleportEqual_21(this, that *DatabaseTLS) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
this.Mode == that.Mode &&
@@ -411,8 +421,8 @@ func deriveTeleportEqual_20(this, that *DatabaseTLS) bool {
this.TrustSystemCertPool == that.TrustSystemCertPool
}
// deriveTeleportEqual_21 returns whether this and that are equal.
func deriveTeleportEqual_21(this, that *AD) bool {
// deriveTeleportEqual_22 returns whether this and that are equal.
func deriveTeleportEqual_22(this, that *AD) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
this.KeytabFile == that.KeytabFile &&
@@ -423,45 +433,45 @@ func deriveTeleportEqual_21(this, that *AD) bool {
this.KDCHostName == that.KDCHostName
}
// deriveTeleportEqual_22 returns whether this and that are equal.
func deriveTeleportEqual_22(this, that *MySQLOptions) bool {
// deriveTeleportEqual_23 returns whether this and that are equal.
func deriveTeleportEqual_23(this, that *MySQLOptions) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
this.ServerVersion == that.ServerVersion
}
// deriveTeleportEqual_23 returns whether this and that are equal.
func deriveTeleportEqual_23(this, that *DatabaseAdminUser) bool {
// deriveTeleportEqual_24 returns whether this and that are equal.
func deriveTeleportEqual_24(this, that *DatabaseAdminUser) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
this.Name == that.Name &&
this.DefaultDatabase == that.DefaultDatabase
}
// deriveTeleportEqual_24 returns whether this and that are equal.
func deriveTeleportEqual_24(this, that *MongoAtlas) bool {
// deriveTeleportEqual_25 returns whether this and that are equal.
func deriveTeleportEqual_25(this, that *MongoAtlas) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
this.Name == that.Name
}
// deriveTeleportEqual_25 returns whether this and that are equal.
func deriveTeleportEqual_25(this, that *OracleOptions) bool {
// deriveTeleportEqual_26 returns whether this and that are equal.
func deriveTeleportEqual_26(this, that *OracleOptions) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
this.AuditUser == that.AuditUser
}
// deriveTeleportEqual_26 returns whether this and that are equal.
func deriveTeleportEqual_26(this, that *Resolution) bool {
// deriveTeleportEqual_27 returns whether this and that are equal.
func deriveTeleportEqual_27(this, that *Resolution) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
this.Width == that.Width &&
this.Height == that.Height
}
// deriveTeleportEqual_27 returns whether this and that are equal.
func deriveTeleportEqual_27(this, that *Rotation) bool {
// deriveTeleportEqual_28 returns whether this and that are equal.
func deriveTeleportEqual_28(this, that *Rotation) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
this.State == that.State &&
@@ -471,36 +481,11 @@ func deriveTeleportEqual_27(this, that *Rotation) bool {
this.Started.Equal(that.Started) &&
this.GracePeriod == that.GracePeriod &&
this.LastRotated.Equal(that.LastRotated) &&
deriveTeleportEqual_31(&this.Schedule, &that.Schedule)
}
// deriveTeleportEqual_28 returns whether this and that are equal.
func deriveTeleportEqual_28(this, that []*OktaAssignmentTargetV1) bool {
if this == nil || that == nil {
return this == nil && that == nil
}
if len(this) != len(that) {
return false
}
for i := 0; i < len(this); i++ {
if !(deriveTeleportEqual_32(this[i], that[i])) {
return false
}
}
return true
deriveTeleportEqual_32(&this.Schedule, &that.Schedule)
}
// deriveTeleportEqual_29 returns whether this and that are equal.
func deriveTeleportEqual_29(this, that *CommandLabelV2) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
this.Period == that.Period &&
deriveTeleportEqual_19(this.Command, that.Command) &&
this.Result == that.Result
}
// deriveTeleportEqual_30 returns whether this and that are equal.
func deriveTeleportEqual_30(this, that []*Header) bool {
func deriveTeleportEqual_29(this, that []*OktaAssignmentTargetV1) bool {
if this == nil || that == nil {
return this == nil && that == nil
}
@@ -515,8 +500,33 @@ func deriveTeleportEqual_30(this, that []*Header) bool {
return true
}
// deriveTeleportEqual_30 returns whether this and that are equal.
func deriveTeleportEqual_30(this, that *CommandLabelV2) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
this.Period == that.Period &&
deriveTeleportEqual_20(this.Command, that.Command) &&
this.Result == that.Result
}
// deriveTeleportEqual_31 returns whether this and that are equal.
func deriveTeleportEqual_31(this, that *RotationSchedule) bool {
func deriveTeleportEqual_31(this, that []*Header) bool {
if this == nil || that == nil {
return this == nil && that == nil
}
if len(this) != len(that) {
return false
}
for i := 0; i < len(this); i++ {
if !(deriveTeleportEqual_34(this[i], that[i])) {
return false
}
}
return true
}
// deriveTeleportEqual_32 returns whether this and that are equal.
func deriveTeleportEqual_32(this, that *RotationSchedule) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
this.UpdateClients.Equal(that.UpdateClients) &&
@@ -524,16 +534,16 @@ func deriveTeleportEqual_31(this, that *RotationSchedule) bool {
this.Standby.Equal(that.Standby)
}
// deriveTeleportEqual_32 returns whether this and that are equal.
func deriveTeleportEqual_32(this, that *OktaAssignmentTargetV1) bool {
// deriveTeleportEqual_33 returns whether this and that are equal.
func deriveTeleportEqual_33(this, that *OktaAssignmentTargetV1) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
this.Type == that.Type &&
this.Id == that.Id
}
// deriveTeleportEqual_33 returns whether this and that are equal.
func deriveTeleportEqual_33(this, that *Header) bool {
// deriveTeleportEqual_34 returns whether this and that are equal.
func deriveTeleportEqual_34(this, that *Header) bool {
return (this == nil && that == nil) ||
this != nil && that != nil &&
this.Name == that.Name &&
+2535 -2217
View File
File diff suppressed because it is too large Load Diff
+140
View File
@@ -0,0 +1,140 @@
/*
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 aws
import (
"net/url"
"strings"
"github.com/gravitational/trace"
)
// IsDocumentDBEndpoint returns true if the input URI is a DocumentDB endpoint.
//
// https://docs.aws.amazon.com/documentdb/latest/developerguide/endpoints.html
func IsDocumentDBEndpoint(uri string) bool {
return isAWSServiceEndpoint(uri, DocumentDBServiceName)
}
// DocumentDBEndpointDetails contains information about a DocumentDB endpoint.
type DocumentDBEndpointDetails struct {
// ClusterID is the identifier of a DocumentDB cluster.
ClusterID string
// InstanceID is the identifier of a DocumentDB instance.
InstanceID string
// Region is the AWS region for the endpoint.
Region string
// EndpointType specifies the type of the endpoint.
EndpointType string
}
// ParseDocumentDBEndpoint parses and extracts info from the provided
// DocumentDB endpoint.
func ParseDocumentDBEndpoint(endpoint string) (*DocumentDBEndpointDetails, error) {
if !strings.HasPrefix(endpoint, "mongodb+srv://") &&
!strings.HasPrefix(endpoint, "mongodb://") {
endpoint = "mongodb://" + endpoint
}
docdbURL, err := url.Parse(endpoint)
if err != nil {
return nil, trace.Wrap(err)
}
endpoint = docdbURL.Hostname()
if strings.HasSuffix(endpoint, AWSCNEndpointSuffix) {
return parseDocumentDBCNEndpoint(endpoint)
}
if strings.HasSuffix(endpoint, AWSEndpointSuffix) {
return parseDocumentDBEndpoint(endpoint)
}
return nil, trace.BadParameter("failed to parse %v as DocumentDB endpoint", endpoint)
}
func parseDocumentDBCNEndpoint(endpoint string) (*DocumentDBEndpointDetails, error) {
// Example:
// my-documentdb-cluster-id.cluster-abcdefghijklmnop.docdb.cn-north-1.amazonaws.com.cn
parts := strings.Split(strings.TrimSuffix(endpoint, AWSCNEndpointSuffix), ".")
if len(parts) != 4 {
return nil, trace.BadParameter("failed to parse %v as DocumentDB CN endpoint", endpoint)
}
if parts[2] != DocumentDBServiceName {
return nil, trace.BadParameter("failed to parse %v as DocumentDB CN endpoint", endpoint)
}
return makeDocumentDBDetails(parts[0], parts[1], parts[3]), nil
}
func parseDocumentDBEndpoint(endpoint string) (*DocumentDBEndpointDetails, error) {
// Examples:
// my-documentdb-cluster-id.cluster-abcdefghijklmnop.us-east-1.docdb.amazonaws.com
// my-documentdb-cluster-id.cluster-ro-abcdefghijklmnop.us-east-1.docdb.amazonaws.com
// my-instance-id.abcdefghijklmnop.us-east-1.docdb.amazonaws.com
parts := strings.Split(strings.TrimSuffix(endpoint, AWSEndpointSuffix), ".")
if len(parts) != 4 {
return nil, trace.BadParameter("failed to parse %v as DocumentDB endpoint", endpoint)
}
if parts[3] != DocumentDBServiceName {
return nil, trace.BadParameter("failed to parse %v as DocumentDB endpoint", endpoint)
}
return makeDocumentDBDetails(parts[0], parts[1], parts[2]), nil
}
func makeDocumentDBDetails(id, endpointTypePart, region string) *DocumentDBEndpointDetails {
endpointType := guessDocumentDBEndpointType(endpointTypePart)
if endpointType == DocumentDBInstanceEndpoint {
return &DocumentDBEndpointDetails{
InstanceID: id,
Region: region,
EndpointType: endpointType,
}
}
return &DocumentDBEndpointDetails{
ClusterID: id,
Region: region,
EndpointType: endpointType,
}
}
func guessDocumentDBEndpointType(endpointTypePart string) string {
switch {
case strings.HasPrefix(endpointTypePart, "cluster-ro-"):
return DocumentDBClusterReaderEndpoint
case strings.HasPrefix(endpointTypePart, "cluster-"):
return DocumentDBClusterEndpoint
default:
return DocumentDBInstanceEndpoint
}
}
const (
// DocumentDBServiceName is the service name for AWS DocumentDB.
//
// TODO(greedy52) support DocumentDB Elastic clusters when IAM Auth support
// is added. Note that Elastic clusters use "docdb-elastic" as the service
// name in the endpoint.
DocumentDBServiceName = "docdb"
// DocumentDBClusterEndpoint specifies a DocumentDB primary/cluster
// endpoint.
DocumentDBClusterEndpoint = "cluster"
// DocumentDBReaderEndpoint specifies a DocumentDB reader endpoint.
DocumentDBClusterReaderEndpoint = "reader"
// DocumentDBInstanceEndpoint specifies a DocumentDB instance endpoint.
DocumentDBInstanceEndpoint = "instance"
)
+109
View File
@@ -0,0 +1,109 @@
/*
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 aws
import (
"testing"
"github.com/gravitational/trace"
"github.com/stretchr/testify/require"
)
func TestParseDocumentDBEndpoint(t *testing.T) {
tests := []struct {
name string
endpoint string
expectIsDocumentDBEndpoint bool
expectDetails *DocumentDBEndpointDetails
expectParseErrorIs func(error) bool
}{
{
name: "DocuementDB cluster",
endpoint: "my-documentdb-cluster-id.cluster-abcdefghijklmnop.us-east-1.docdb.amazonaws.com:27017",
expectIsDocumentDBEndpoint: true,
expectDetails: &DocumentDBEndpointDetails{
ClusterID: "my-documentdb-cluster-id",
Region: "us-east-1",
EndpointType: "cluster",
},
},
{
name: "DocuementDB cluster in mongo URL format",
endpoint: "mongodb://my-documentdb-cluster-id.cluster-abcdefghijklmnop.us-east-1.docdb.amazonaws.com:27017/?retryWrites=false",
expectIsDocumentDBEndpoint: true,
expectDetails: &DocumentDBEndpointDetails{
ClusterID: "my-documentdb-cluster-id",
Region: "us-east-1",
EndpointType: "cluster",
},
},
{
name: "DocuementDB cluster reader",
endpoint: "my-documentdb-cluster-id.cluster-ro-abcdefghijklmnop.us-east-1.docdb.amazonaws.com",
expectIsDocumentDBEndpoint: true,
expectDetails: &DocumentDBEndpointDetails{
ClusterID: "my-documentdb-cluster-id",
Region: "us-east-1",
EndpointType: "reader",
},
},
{
name: "DocuementDB instance",
endpoint: "my-instance-id.abcdefghijklmnop.us-east-1.docdb.amazonaws.com",
expectIsDocumentDBEndpoint: true,
expectDetails: &DocumentDBEndpointDetails{
InstanceID: "my-instance-id",
Region: "us-east-1",
EndpointType: "instance",
},
},
{
name: "DocuementDB CN",
endpoint: "my-documentdb-cluster-id.cluster-abcdefghijklmnop.docdb.cn-north-1.amazonaws.com.cn",
expectIsDocumentDBEndpoint: true,
expectDetails: &DocumentDBEndpointDetails{
ClusterID: "my-documentdb-cluster-id",
Region: "cn-north-1",
EndpointType: "cluster",
},
},
{
name: "RDS instance fails",
endpoint: "aurora-instance-1.abcdefghijklmnop.us-west-1.rds.amazonaws.com:5432",
expectIsDocumentDBEndpoint: false,
expectParseErrorIs: trace.IsBadParameter,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
require.Equal(t, test.expectIsDocumentDBEndpoint, IsDocumentDBEndpoint(test.endpoint))
actualDetails, err := ParseDocumentDBEndpoint(test.endpoint)
if test.expectParseErrorIs != nil {
require.Error(t, err)
require.True(t, test.expectParseErrorIs(err))
} else {
require.NoError(t, err)
require.Equal(t, test.expectDetails, actualDetails)
}
})
}
}
@@ -62,7 +62,7 @@ type VnetServiceClient interface {
// be fetched (due to e.g., a network error or an expired cert).
ListDNSZones(ctx context.Context, in *ListDNSZonesRequest, opts ...grpc.CallOption) (*ListDNSZonesResponse, error)
// GetBackgroundItemStatus returns the status of the background item responsible for launching
// VNet daemon. macOS only.
// VNet daemon. macOS only. tsh must be compiled with the vnetdaemon build tag.
GetBackgroundItemStatus(ctx context.Context, in *GetBackgroundItemStatusRequest, opts ...grpc.CallOption) (*GetBackgroundItemStatusResponse, error)
}
@@ -135,7 +135,7 @@ type VnetServiceServer interface {
// be fetched (due to e.g., a network error or an expired cert).
ListDNSZones(context.Context, *ListDNSZonesRequest) (*ListDNSZonesResponse, error)
// GetBackgroundItemStatus returns the status of the background item responsible for launching
// VNet daemon. macOS only.
// VNet daemon. macOS only. tsh must be compiled with the vnetdaemon build tag.
GetBackgroundItemStatus(context.Context, *GetBackgroundItemStatusRequest) (*GetBackgroundItemStatusResponse, error)
mustEmbedUnimplementedVnetServiceServer()
}
+1 -1
View File
@@ -563,7 +563,7 @@ func testKubeGatewayCertRenewal(ctx context.Context, t *testing.T, params kubeGa
func checkKubeconfigPathInCommandEnv(t *testing.T, daemonService *daemon.Service, gw gateway.Gateway, wantKubeconfigPath string) {
t.Helper()
cmds, err := daemonService.GetGatewayCLICommand(gw)
cmds, err := daemonService.GetGatewayCLICommand(context.Background(), gw)
require.NoError(t, err)
require.Equal(t, []string{"KUBECONFIG=" + wantKubeconfigPath}, cmds.Exec.Env)
require.Equal(t, []string{"KUBECONFIG=" + wantKubeconfigPath}, cmds.Preview.Env)
+47 -10
View File
@@ -19,6 +19,7 @@
package dbcmd
import (
"context"
"fmt"
"net/url"
"os"
@@ -173,7 +174,7 @@ func NewCmdBuilder(tc *client.TeleportClient, profile *client.ProfileStatus,
// If CLICommandBuilder's options.tolerateMissingCLIClient is set to true, GetConnectCommand
// shouldn't return an error if it cannot locate a client binary. Check WithTolerateMissingCLIClient
// docs for more details.
func (c *CLICommandBuilder) GetConnectCommand() (*exec.Cmd, error) {
func (c *CLICommandBuilder) GetConnectCommand(ctx context.Context) (*exec.Cmd, error) {
switch c.db.Protocol {
case defaults.ProtocolPostgres:
return c.getPostgresCommand(), nil
@@ -185,7 +186,7 @@ func (c *CLICommandBuilder) GetConnectCommand() (*exec.Cmd, error) {
return c.getMySQLCommand()
case defaults.ProtocolMongoDB:
return c.getMongoCommand(), nil
return c.getMongoCommand(ctx)
case defaults.ProtocolRedis:
return c.getRedisCommand(), nil
@@ -232,8 +233,7 @@ type CommandAlternative struct {
// GetConnectCommandAlternatives returns optional connection commands for protocols that offer multiple options.
// Otherwise, it falls back to GetConnectCommand.
// The keys in the returned map are command descriptions suitable for display to the end user.
func (c *CLICommandBuilder) GetConnectCommandAlternatives() ([]CommandAlternative, error) {
func (c *CLICommandBuilder) GetConnectCommandAlternatives(ctx context.Context) ([]CommandAlternative, error) {
switch c.db.Protocol {
case defaults.ProtocolElasticsearch:
return c.getElasticsearchAlternativeCommands(), nil
@@ -241,7 +241,7 @@ func (c *CLICommandBuilder) GetConnectCommandAlternatives() ([]CommandAlternativ
return c.getOpenSearchAlternativeCommands(), nil
}
cmd, err := c.GetConnectCommand()
cmd, err := c.GetConnectCommand(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
@@ -464,16 +464,25 @@ func (c *CLICommandBuilder) isSqlcmdAvailable() bool {
return c.isBinAvailable(sqlcmdBin)
}
func (c *CLICommandBuilder) shouldUseMongoshBin() bool {
func (c *CLICommandBuilder) shouldUseMongoshBin(db types.Database) bool {
// DocumentDB prefers the legacy "mongo" client.
if db.GetType() == types.DatabaseTypeDocumentDB {
return c.isMongoshBinAvailable() && !c.isBinAvailable(mongoBin)
}
// Use "mongosh" if available.
// If not, use legacy "mongo" if available.
// If both are not available, pick "mongosh" in print out.
return c.isMongoshBinAvailable() || !c.isBinAvailable(mongoBin)
}
func (c *CLICommandBuilder) getMongoCommand() *exec.Cmd {
func (c *CLICommandBuilder) getMongoCommand(ctx context.Context) (*exec.Cmd, error) {
db, err := c.getDatabase(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
// look for `mongosh`
useMongosh := c.shouldUseMongoshBin()
useMongosh := c.shouldUseMongoshBin(db)
var args []string
@@ -519,11 +528,27 @@ func (c *CLICommandBuilder) getMongoCommand() *exec.Cmd {
// use `mongosh` if available
if useMongosh {
return exec.Command(mongoshBin, args...)
// DocumentDB does not support retryable writes.
// https://docs.aws.amazon.com/documentdb/latest/developerguide/functional-differences.html#functional-differences.retryable-writes
if db.GetType() == types.DatabaseTypeDocumentDB {
args = append(args, "--retryWrites=false")
}
return exec.Command(mongoshBin, args...), nil
}
// fall back to `mongo` if `mongosh` isn't found
return exec.Command(mongoBin, args...)
return exec.Command(mongoBin, args...), nil
}
func (c *CLICommandBuilder) getDatabase(ctx context.Context) (types.Database, error) {
// Technically, we can just use tc to get the database. But caller may have
// extra logic so rely on the callback for now.
if c.options.getDatabase == nil {
return nil, trace.NotFound("missing GetDatabaseFunc")
}
db, err := c.options.getDatabase(ctx, c.tc, c.db.ServiceName)
return db, trace.Wrap(err)
}
func (c *CLICommandBuilder) getMongoAddress() string {
@@ -881,6 +906,7 @@ type connectionCommandOpts struct {
exe Execer
password string
gcp types.GCPCloudSQL
getDatabase GetDatabaseFunc
}
// ConnectCommandFunc is a type for functions returned by the "With*" functions in this package.
@@ -971,12 +997,23 @@ func WithExecer(exe Execer) ConnectCommandFunc {
}
// WithGCP adds GCP metadata for the database command to access.
// TODO(greedy52) use GetDatabaseFunc instead.
func WithGCP(gcp types.GCPCloudSQL) ConnectCommandFunc {
return func(opts *connectionCommandOpts) {
opts.gcp = gcp
}
}
// GetDatabaseFunc is a callback to retrieve types.Database.
type GetDatabaseFunc func(context.Context, *client.TeleportClient, string) (types.Database, error)
// WithGetDatabaseFunc provides a callback to retrieve types.Database.
func WithGetDatabaseFunc(f GetDatabaseFunc) ConnectCommandFunc {
return func(opts *connectionCommandOpts) {
opts.getDatabase = f
}
}
const (
// envVarMongoServerSelectionTimeoutMS is the environment variable that
// controls the server selection timeout used for MongoDB clients.
+75 -7
View File
@@ -19,6 +19,7 @@
package dbcmd
import (
"context"
"errors"
"strings"
"testing"
@@ -322,6 +323,7 @@ func TestCLICommandBuilderGetConnectCommand(t *testing.T) {
name: "mongodb (legacy)",
dbProtocol: defaults.ProtocolMongoDB,
databaseName: "mydb",
opts: []ConnectCommandFunc{withMongoDBAtlasDatabase()},
execer: &fakeExec{
execOutput: map[string][]byte{
"mongo": []byte("legacy"),
@@ -338,7 +340,7 @@ func TestCLICommandBuilderGetConnectCommand(t *testing.T) {
name: "mongodb no TLS (legacy)",
dbProtocol: defaults.ProtocolMongoDB,
databaseName: "mydb",
opts: []ConnectCommandFunc{WithNoTLS()},
opts: []ConnectCommandFunc{WithNoTLS(), withMongoDBAtlasDatabase()},
execer: &fakeExec{
execOutput: map[string][]byte{
"mongo": []byte("legacy"),
@@ -353,6 +355,7 @@ func TestCLICommandBuilderGetConnectCommand(t *testing.T) {
name: "mongosh no CA",
dbProtocol: defaults.ProtocolMongoDB,
databaseName: "mydb",
opts: []ConnectCommandFunc{withMongoDBAtlasDatabase()},
execer: &fakeExec{
execOutput: map[string][]byte{
"mongosh": []byte("1.1.6"),
@@ -370,7 +373,9 @@ func TestCLICommandBuilderGetConnectCommand(t *testing.T) {
dbProtocol: defaults.ProtocolMongoDB,
databaseName: "mydb",
opts: []ConnectCommandFunc{
WithLocalProxy("localhost", 12345, "/tmp/keys/example.com/cas/example.com.pem")},
WithLocalProxy("localhost", 12345, "/tmp/keys/example.com/cas/example.com.pem"),
withMongoDBAtlasDatabase(),
},
execer: &fakeExec{
execOutput: map[string][]byte{
"mongosh": []byte("1.1.6"),
@@ -387,7 +392,7 @@ func TestCLICommandBuilderGetConnectCommand(t *testing.T) {
name: "mongosh no TLS",
dbProtocol: defaults.ProtocolMongoDB,
databaseName: "mydb",
opts: []ConnectCommandFunc{WithNoTLS()},
opts: []ConnectCommandFunc{WithNoTLS(), withMongoDBAtlasDatabase()},
execer: &fakeExec{
execOutput: map[string][]byte{
"mongosh": []byte("1.1.6"),
@@ -401,7 +406,7 @@ func TestCLICommandBuilderGetConnectCommand(t *testing.T) {
name: "mongosh preferred",
dbProtocol: defaults.ProtocolMongoDB,
databaseName: "mydb",
opts: []ConnectCommandFunc{WithNoTLS()},
opts: []ConnectCommandFunc{WithNoTLS(), withMongoDBAtlasDatabase()},
execer: &fakeExec{
execOutput: map[string][]byte{}, // Cannot find either bin.
},
@@ -409,6 +414,39 @@ func TestCLICommandBuilderGetConnectCommand(t *testing.T) {
"mongodb://localhost:12345/mydb?serverSelectionTimeoutMS=5000",
},
},
{
name: "DocumentDB",
dbProtocol: defaults.ProtocolMongoDB,
databaseName: "docdb",
opts: []ConnectCommandFunc{WithNoTLS(), withDocumentDBDatabase()},
execer: &fakeExec{
execOutput: map[string][]byte{
// When both are available, legacy mongo is preferred.
"mongo": []byte("legacy"),
"mongosh": []byte("1.1.6"),
},
},
cmd: []string{"mongo",
"mongodb://localhost:12345/docdb?serverSelectionTimeoutMS=5000",
},
wantErr: false,
},
{
name: "DocumentDB mongosh",
dbProtocol: defaults.ProtocolMongoDB,
databaseName: "docdb",
opts: []ConnectCommandFunc{WithNoTLS(), withDocumentDBDatabase()},
execer: &fakeExec{
execOutput: map[string][]byte{
"mongosh": []byte("1.1.6"),
},
},
cmd: []string{"mongosh",
"mongodb://localhost:12345/docdb?serverSelectionTimeoutMS=5000",
"--retryWrites=false",
},
wantErr: false,
},
{
name: "sqlserver",
dbProtocol: defaults.ProtocolSQLServer,
@@ -749,7 +787,7 @@ func TestCLICommandBuilderGetConnectCommand(t *testing.T) {
c := NewCmdBuilder(tc, profile, database, "root", opts...)
c.uid = utils.NewFakeUID()
got, err := c.GetConnectCommand()
got, err := c.GetConnectCommand(context.Background())
if tt.wantErr {
require.Error(t, err)
return
@@ -913,7 +951,7 @@ func TestCLICommandBuilderGetConnectCommandAlternatives(t *testing.T) {
c := NewCmdBuilder(tc, profile, database, "root", opts...)
c.uid = utils.NewFakeUID()
commandOptions, err := c.GetConnectCommandAlternatives()
commandOptions, err := c.GetConnectCommandAlternatives(context.Background())
if tt.wantErr {
require.Error(t, err)
return
@@ -1001,7 +1039,7 @@ func TestConvertCommandError(t *testing.T) {
c := NewCmdBuilder(tc, profile, database, "root", opts...)
c.uid = utils.NewFakeUID()
cmd, err := c.GetConnectCommand()
cmd, err := c.GetConnectCommand(context.Background())
require.NoError(t, err)
// make sure the expected test bin is the command bin we got
@@ -1016,3 +1054,33 @@ func TestConvertCommandError(t *testing.T) {
})
}
}
func withMongoDBAtlasDatabase() ConnectCommandFunc {
return WithGetDatabaseFunc(func(context.Context, *client.TeleportClient, string) (types.Database, error) {
db, err := types.NewDatabaseV3(
types.Metadata{
Name: "mongodb-atlas",
},
types.DatabaseSpecV3{
Protocol: types.DatabaseProtocolMongoDB,
URI: "mongodb+srv://my-cluster.abcdefy.mongodb.net",
},
)
return db, trace.Wrap(err)
})
}
func withDocumentDBDatabase() ConnectCommandFunc {
return WithGetDatabaseFunc(func(context.Context, *client.TeleportClient, string) (types.Database, error) {
db, err := types.NewDatabaseV3(
types.Metadata{
Name: "docdb",
},
types.DatabaseSpecV3{
Protocol: types.DatabaseProtocolMongoDB,
URI: "my-documentdb-cluster-id.cluster-abcdefghijklmnop.us-east-1.docdb.amazonaws.com:27017",
},
)
return db, trace.Wrap(err)
})
}
+5 -3
View File
@@ -101,6 +101,7 @@ func (s *Server) shouldInitCACertLocked(database types.Database) bool {
// Can only download it for cloud-hosted instances.
switch database.GetType() {
case types.DatabaseTypeRDS,
types.DatabaseTypeDocumentDB,
types.DatabaseTypeRedshift,
types.DatabaseTypeRedshiftServerless,
types.DatabaseTypeElastiCache,
@@ -164,7 +165,8 @@ func (s *Server) getCACertPaths(database types.Database) ([]string, error) {
switch database.GetType() {
// All RDS instances share the same root CA (per AWS region) which can be
// downloaded from a well-known URL.
case types.DatabaseTypeRDS:
// DocumentDB uses same certs as RDS.
case types.DatabaseTypeRDS, types.DatabaseTypeDocumentDB:
return []string{filepath.Join(s.cfg.DataDir, filepath.Base(rdsCAURLForDatabase(database)))}, nil
// All Redshift instances share the same root CA which can be downloaded
@@ -307,7 +309,7 @@ func NewRealDownloader() CADownloader {
// Download downloads CA certificate for the provided cloud database instance.
func (d *realDownloader) Download(ctx context.Context, database types.Database, hint string) ([]byte, []byte, error) {
switch database.GetType() {
case types.DatabaseTypeRDS:
case types.DatabaseTypeRDS, types.DatabaseTypeDocumentDB:
return d.downloadFromURL(rdsCAURLForDatabase(database))
case types.DatabaseTypeRedshift,
types.DatabaseTypeRedshiftServerless:
@@ -336,7 +338,7 @@ func (d *realDownloader) Download(ctx context.Context, database types.Database,
// GetVersion returns the CA version for the provided database.
func (d *realDownloader) GetVersion(ctx context.Context, database types.Database, hint string) ([]byte, error) {
switch database.GetType() {
case types.DatabaseTypeRDS:
case types.DatabaseTypeRDS, types.DatabaseTypeDocumentDB:
return d.getVersionFromURL(database, rdsCAURLForDatabase(database))
case types.DatabaseTypeRedshift,
types.DatabaseTypeRedshiftServerless:
+29 -1
View File
@@ -139,8 +139,31 @@ func TestInitCACert(t *testing.T) {
})
require.NoError(t, err)
docdb, err := types.NewDatabaseV3(types.Metadata{
Name: "docdb",
}, types.DatabaseSpecV3{
Protocol: defaults.ProtocolMongoDB,
URI: "localhost:27017",
AWS: types.AWS{
Region: "us-east-1",
DocumentDB: types.DocumentDB{
ClusterID: "docdb",
},
},
})
require.NoError(t, err)
allDatabases := []types.Database{
selfHosted, rds, rdsWithCert, redshift, redshiftServerless, cloudSQL, azureMySQL, memoryDB, mongodbAtlas,
selfHosted,
rds,
rdsWithCert,
redshift,
redshiftServerless,
cloudSQL,
azureMySQL,
memoryDB,
mongodbAtlas,
docdb,
}
tests := []struct {
@@ -193,6 +216,11 @@ func TestInitCACert(t *testing.T) {
database: mongodbAtlas.GetName(),
cert: fixtures.TLSCACertPEM,
},
{
desc: "should download DocumentDB CA when it's not set",
database: docdb.GetName(),
cert: fixtures.TLSCACertPEM,
},
}
databaseServer := testCtx.setupDatabaseServer(ctx, t, agentParams{
+5 -1
View File
@@ -163,7 +163,8 @@ func (e *Engine) getConnectionOptions(ctx context.Context, sessionCtx *common.Se
}
func (e *Engine) getAuthenticator(ctx context.Context, sessionCtx *common.Session) (auth.Authenticator, error) {
isAtlasDB := sessionCtx.Database.GetType() == types.DatabaseTypeMongoAtlas
dbType := sessionCtx.Database.GetType()
isAtlasDB := dbType == types.DatabaseTypeMongoAtlas
// Currently, the MongoDB Atlas IAM Authentication doesn't work with IAM
// users. Here we provide a better error message to the users.
@@ -174,6 +175,9 @@ func (e *Engine) getAuthenticator(ctx context.Context, sessionCtx *common.Sessio
switch {
case isAtlasDB && awsutils.IsRoleARN(sessionCtx.DatabaseUser):
return e.getAWSAuthenticator(ctx, sessionCtx)
case dbType == types.DatabaseTypeDocumentDB:
// DocumentDB uses the same the IAM authenticator as MongoDB Atlas.
return e.getAWSAuthenticator(ctx, sessionCtx)
default:
e.Log.DebugContext(e.Context, "Authenticating to database using certificates.")
authenticator, err := auth.CreateAuthenticator(auth.MongoDBX509, &auth.Cred{
@@ -45,7 +45,7 @@ func (s *Handler) CreateGateway(ctx context.Context, req *api.CreateGatewayReque
return nil, trace.Wrap(err)
}
apiGateway, err := s.newAPIGateway(gateway)
apiGateway, err := s.newAPIGateway(ctx, gateway)
if err != nil {
return nil, trace.Wrap(err)
}
@@ -59,7 +59,7 @@ func (s *Handler) ListGateways(ctx context.Context, req *api.ListGatewaysRequest
apiGws := make([]*api.Gateway, 0, len(gws))
for _, gw := range gws {
apiGateway, err := s.newAPIGateway(gw)
apiGateway, err := s.newAPIGateway(ctx, gw)
if err != nil {
return nil, trace.Wrap(err)
}
@@ -81,8 +81,8 @@ func (s *Handler) RemoveGateway(ctx context.Context, req *api.RemoveGatewayReque
return &api.EmptyResponse{}, nil
}
func (s *Handler) newAPIGateway(gateway gateway.Gateway) (*api.Gateway, error) {
cmds, err := s.DaemonService.GetGatewayCLICommand(gateway)
func (s *Handler) newAPIGateway(ctx context.Context, gateway gateway.Gateway) (*api.Gateway, error) {
cmds, err := s.DaemonService.GetGatewayCLICommand(ctx, gateway)
if err != nil {
return nil, trace.Wrap(err)
}
@@ -124,7 +124,7 @@ func (s *Handler) SetGatewayTargetSubresourceName(ctx context.Context, req *api.
return nil, trace.Wrap(err)
}
apiGateway, err := s.newAPIGateway(gateway)
apiGateway, err := s.newAPIGateway(ctx, gateway)
if err != nil {
return nil, trace.Wrap(err)
}
@@ -139,7 +139,7 @@ func (s *Handler) SetGatewayLocalPort(ctx context.Context, req *api.SetGatewayLo
return nil, trace.Wrap(err)
}
apiGateway, err := s.newAPIGateway(gateway)
apiGateway, err := s.newAPIGateway(ctx, gateway)
if err != nil {
return nil, trace.Wrap(err)
}
+1 -1
View File
@@ -53,7 +53,7 @@ func (c *Cluster) GetDatabase(ctx context.Context, authClient authclient.ClientI
dbName := dbURI.GetDbName()
err := AddMetadataToRetryableError(ctx, func() error {
databases, err := apiclient.GetAllResources[types.DatabaseServer](ctx, authClient, &proto.ListResourcesRequest{
Namespace: c.clusterClient.Namespace,
Namespace: defaults.Namespace,
ResourceType: types.KindDatabaseServer,
PredicateExpression: fmt.Sprintf(`name == "%s"`, dbName),
})
+20 -5
View File
@@ -19,10 +19,15 @@
package cmd
import (
"context"
"os/exec"
"sync"
"github.com/gravitational/trace"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/auth/authclient"
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/client/db/dbcmd"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/teleterm/clusters"
@@ -43,12 +48,12 @@ type Cmds struct {
}
// NewDBCLICommand creates CLI commands for database gateway.
func NewDBCLICommand(cluster *clusters.Cluster, gateway gateway.Gateway) (Cmds, error) {
cmds, err := newDBCLICommandWithExecer(cluster, gateway, dbcmd.SystemExecer{})
func NewDBCLICommand(ctx context.Context, cluster *clusters.Cluster, gateway gateway.Gateway, authClient authclient.ClientI) (Cmds, error) {
cmds, err := newDBCLICommandWithExecer(ctx, cluster, gateway, dbcmd.SystemExecer{}, authClient)
return cmds, trace.Wrap(err)
}
func newDBCLICommandWithExecer(cluster *clusters.Cluster, gateway gateway.Gateway, execer dbcmd.Execer) (Cmds, error) {
func newDBCLICommandWithExecer(ctx context.Context, cluster *clusters.Cluster, gateway gateway.Gateway, execer dbcmd.Execer, authClient authclient.ClientI) (Cmds, error) {
routeToDb := tlsca.RouteToDatabase{
ServiceName: gateway.TargetName(),
Protocol: gateway.Protocol(),
@@ -56,12 +61,22 @@ func newDBCLICommandWithExecer(cluster *clusters.Cluster, gateway gateway.Gatewa
Database: gateway.TargetSubresourceName(),
}
var getDatabaseOnce sync.Once
var getDatabaseError error
var database types.Database
opts := []dbcmd.ConnectCommandFunc{
dbcmd.WithLogger(gateway.Log()),
dbcmd.WithLocalProxy(gateway.LocalAddress(), gateway.LocalPortInt(), ""),
dbcmd.WithNoTLS(),
dbcmd.WithTolerateMissingCLIClient(),
dbcmd.WithExecer(execer),
dbcmd.WithGetDatabaseFunc(func(ctx context.Context, _ *client.TeleportClient, _ string) (types.Database, error) {
getDatabaseOnce.Do(func() {
database, getDatabaseError = cluster.GetDatabase(ctx, authClient, gateway.TargetURI())
})
return database, trace.Wrap(getDatabaseError)
}),
}
switch gateway.Protocol() {
@@ -75,12 +90,12 @@ func newDBCLICommandWithExecer(cluster *clusters.Cluster, gateway gateway.Gatewa
previewOpts := append(opts, dbcmd.WithPrintFormat())
execCmd, err := clusters.NewDBCLICmdBuilder(cluster, routeToDb, opts...).GetConnectCommand()
execCmd, err := clusters.NewDBCLICmdBuilder(cluster, routeToDb, opts...).GetConnectCommand(ctx)
if err != nil {
return Cmds{}, trace.Wrap(err)
}
previewCmd, err := clusters.NewDBCLICmdBuilder(cluster, routeToDb, previewOpts...).GetConnectCommand()
previewCmd, err := clusters.NewDBCLICmdBuilder(cluster, routeToDb, previewOpts...).GetConnectCommand(ctx)
if err != nil {
return Cmds{}, trace.Wrap(err)
}
+38 -1
View File
@@ -19,14 +19,19 @@
package cmd
import (
"context"
"fmt"
"os/exec"
"path/filepath"
"testing"
"github.com/gravitational/trace"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/auth/authclient"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/teleterm/api/uri"
"github.com/gravitational/teleport/lib/teleterm/clusters"
@@ -67,6 +72,15 @@ func (m fakeDatabaseGateway) LocalPortInt() int { return 8888 }
func (m fakeDatabaseGateway) LocalPort() string { return "8888" }
func TestNewDBCLICommand(t *testing.T) {
// TODO mock other types
authClient := &mockAuthClient{
database: &types.DatabaseV3{
Spec: types.DatabaseSpecV3{
Protocol: types.DatabaseProtocolMongoDB,
},
},
}
testCases := []struct {
name string
targetSubresourceName string
@@ -112,7 +126,7 @@ func TestNewDBCLICommand(t *testing.T) {
protocol: tc.protocol,
}
cmds, err := newDBCLICommandWithExecer(&cluster, mockGateway, fakeExec{})
cmds, err := newDBCLICommandWithExecer(context.Background(), &cluster, mockGateway, fakeExec{}, authClient)
require.NoError(t, err)
tc.checkCmds(t, mockGateway, cmds)
@@ -120,6 +134,29 @@ func TestNewDBCLICommand(t *testing.T) {
}
}
type mockAuthClient struct {
authclient.ClientI
database *types.DatabaseV3
}
func (m *mockAuthClient) GetResources(_ context.Context, _ *proto.ListResourcesRequest) (*proto.ListResourcesResponse, error) {
if m.database == nil {
return nil, trace.NotFound("not found")
}
return &proto.ListResourcesResponse{
Resources: []*proto.PaginatedResource{{
Resource: &proto.PaginatedResource_DatabaseServer{
DatabaseServer: &types.DatabaseServerV3{
Spec: types.DatabaseServerSpecV3{
Database: m.database,
},
},
},
}},
TotalCount: 1,
}, nil
}
func checkMongoCmds(t *testing.T, gw fakeDatabaseGateway, cmds Cmds) {
t.Helper()
require.Len(t, cmds.Exec.Args, 2)
+7 -2
View File
@@ -479,7 +479,7 @@ func (s *Service) ListGateways() []gateway.Gateway {
}
// GetGatewayCLICommand creates the CLI command used for the provided gateway.
func (s *Service) GetGatewayCLICommand(gateway gateway.Gateway) (cmd.Cmds, error) {
func (s *Service) GetGatewayCLICommand(ctx context.Context, gateway gateway.Gateway) (cmd.Cmds, error) {
targetURI := gateway.TargetURI()
switch {
case targetURI.IsDB():
@@ -488,7 +488,12 @@ func (s *Service) GetGatewayCLICommand(gateway gateway.Gateway) (cmd.Cmds, error
return cmd.Cmds{}, trace.Wrap(err)
}
cmds, err := cmd.NewDBCLICommand(cluster, gateway)
clusterClient, err := s.GetCachedClient(ctx, cluster.URI)
if err != nil {
return cmd.Cmds{}, trace.Wrap(err)
}
cmds, err := cmd.NewDBCLICommand(ctx, cluster, gateway, clusterClient.AuthClient)
return cmds, trace.Wrap(err)
case targetURI.IsKube():
+5 -6
View File
@@ -24,6 +24,7 @@ import (
"net"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
"time"
@@ -675,10 +676,8 @@ func TestGetGatewayCLICommand(t *testing.T) {
checkError: require.NoError,
checkCmds: func(t *testing.T, cmds cmd.Cmds) {
t.Helper()
require.Len(t, cmds.Exec.Args, 2)
require.Contains(t, cmds.Exec.Args[1], "subresource-name")
require.Len(t, cmds.Preview.Args, 2)
require.Contains(t, cmds.Preview.Args[1], "subresource-name")
require.Contains(t, strings.Join(cmds.Exec.Args, " "), "subresource-name")
require.Contains(t, strings.Join(cmds.Preview.Args, " "), "subresource-name")
},
},
{
@@ -697,7 +696,7 @@ func TestGetGatewayCLICommand(t *testing.T) {
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
cmds, err := daemon.GetGatewayCLICommand(test.inputGateway)
cmds, err := daemon.GetGatewayCLICommand(context.Background(), test.inputGateway)
test.checkError(t, err)
test.checkCmds(t, cmds)
})
@@ -714,7 +713,7 @@ func (m fakeGateway) TargetURI() uri.ResourceURI { return m.targetURI }
func (m fakeGateway) TargetName() string { return m.targetURI.GetDbName() + m.targetURI.GetKubeName() }
func (m fakeGateway) TargetUser() string { return "alice" }
func (m fakeGateway) TargetSubresourceName() string { return m.subresourceName }
func (m fakeGateway) Protocol() string { return defaults.ProtocolMongoDB }
func (m fakeGateway) Protocol() string { return defaults.ProtocolSQLServer }
func (m fakeGateway) Log() *logrus.Entry { return nil }
func (m fakeGateway) LocalAddress() string { return "localhost" }
func (m fakeGateway) LocalPortInt() int { return 8888 }
+12 -2
View File
@@ -539,7 +539,8 @@ func onDatabaseConfig(cf *CLIConf) error {
cmd, err := dbcmd.NewCmdBuilder(tc, profile, *database, rootCluster,
dbcmd.WithPrintFormat(),
dbcmd.WithLogger(log),
).GetConnectCommand()
dbcmd.WithGetDatabaseFunc(getDatabase),
).GetConnectCommand(cf.Context)
if err != nil {
return trace.Wrap(err)
}
@@ -651,6 +652,7 @@ func maybeStartLocalProxy(ctx context.Context, cf *CLIConf,
host := "localhost"
cmdOpts := []dbcmd.ConnectCommandFunc{
dbcmd.WithLocalProxy(host, addr.Port(0), profile.CACertPathForCluster(rootClusterName)),
dbcmd.WithGetDatabaseFunc(dbInfo.getDatabaseForDBCmd),
}
if requires.tunnel {
cmdOpts = append(cmdOpts, dbcmd.WithNoTLS())
@@ -784,7 +786,7 @@ func onDatabaseConnect(cf *CLIConf) error {
}
bb := dbcmd.NewCmdBuilder(tc, profile, dbInfo.RouteToDatabase, rootClusterName, opts...)
cmd, err := bb.GetConnectCommand()
cmd, err := bb.GetConnectCommand(cf.Context)
if err != nil {
return trace.Wrap(err)
}
@@ -1031,6 +1033,14 @@ func (d *databaseInfo) GetDatabase(ctx context.Context, tc *client.TeleportClien
return d.database.Copy(), nil
}
// getDatabaseForDBCmd is a callback for dbcmd connect option.
func (d *databaseInfo) getDatabaseForDBCmd(ctx context.Context, tc *client.TeleportClient, serviceName string) (types.Database, error) {
if serviceName != d.ServiceName {
return nil, trace.BadParameter("expect database service %s but got %s", d.ServiceName, serviceName)
}
return d.GetDatabase(ctx, tc)
}
// chooseOneDatabase is a helper func that returns either the only database in a
// list of databases or returns a database that matches the selector name
// or unambiguous discovered name exactly, otherwise an error.
+2 -1
View File
@@ -230,6 +230,7 @@ func onProxyCommandDB(cf *CLIConf) error {
dbcmd.WithLogger(log),
dbcmd.WithPrintFormat(),
dbcmd.WithTolerateMissingCLIClient(),
dbcmd.WithGetDatabaseFunc(dbInfo.getDatabaseForDBCmd),
}
if opts, err = maybeAddDBUserPassword(cf, tc, dbInfo, opts); err != nil {
return trace.Wrap(err)
@@ -240,7 +241,7 @@ func onProxyCommandDB(cf *CLIConf) error {
commands, err := dbcmd.NewCmdBuilder(tc, profile, dbInfo.RouteToDatabase, rootCluster,
opts...,
).GetConnectCommandAlternatives()
).GetConnectCommandAlternatives(cf.Context)
if err != nil {
return trace.Wrap(err)
}
+4 -2
View File
@@ -3883,7 +3883,8 @@ func TestSerializeDatabases(t *testing.T) {
"memorydb": {},
"opensearch": {},
"rdsproxy": {},
"redshift_serverless": {}
"redshift_serverless": {},
"docdb": {}
},
"mysql": {},
"oracle": {
@@ -3915,7 +3916,8 @@ func TestSerializeDatabases(t *testing.T) {
"memorydb": {},
"opensearch": {},
"rdsproxy": {},
"redshift_serverless": {}
"redshift_serverless": {},
"docdb": {}
},
"azure": {
"redis": {}