github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/types/database.go (about) 1 /* 2 Copyright 2021 Gravitational, Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package types 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "log/slog" 24 "regexp" 25 "strings" 26 "time" 27 28 "github.com/gravitational/trace" 29 30 "github.com/gravitational/teleport/api/types/compare" 31 "github.com/gravitational/teleport/api/utils" 32 atlasutils "github.com/gravitational/teleport/api/utils/atlas" 33 awsutils "github.com/gravitational/teleport/api/utils/aws" 34 azureutils "github.com/gravitational/teleport/api/utils/azure" 35 ) 36 37 var _ compare.IsEqual[Database] = (*DatabaseV3)(nil) 38 39 // Database represents a single database proxied by a database server. 40 type Database interface { 41 // ResourceWithLabels provides common resource methods. 42 ResourceWithLabels 43 // GetNamespace returns the database namespace. 44 GetNamespace() string 45 // GetStaticLabels returns the database static labels. 46 GetStaticLabels() map[string]string 47 // SetStaticLabels sets the database static labels. 48 SetStaticLabels(map[string]string) 49 // GetDynamicLabels returns the database dynamic labels. 50 GetDynamicLabels() map[string]CommandLabel 51 // SetDynamicLabels sets the database dynamic labels. 52 SetDynamicLabels(map[string]CommandLabel) 53 // String returns string representation of the database. 54 String() string 55 // GetDescription returns the database description. 56 GetDescription() string 57 // GetProtocol returns the database protocol. 58 GetProtocol() string 59 // GetURI returns the database connection endpoint. 60 GetURI() string 61 // SetURI sets the database connection endpoint. 62 SetURI(string) 63 // GetCA returns the database CA certificate. 64 GetCA() string 65 // SetCA sets the database CA certificate in the Spec.TLS field. 66 SetCA(string) 67 // GetTLS returns the database TLS configuration. 68 GetTLS() DatabaseTLS 69 // SetStatusCA sets the database CA certificate in the status field. 70 SetStatusCA(string) 71 // GetStatusCA gets the database CA certificate in the status field. 72 GetStatusCA() string 73 // GetMySQL returns the database options from spec. 74 GetMySQL() MySQLOptions 75 // GetOracle returns the database options from spec. 76 GetOracle() OracleOptions 77 // GetMySQLServerVersion returns the MySQL server version either from configuration or 78 // reported by the database. 79 GetMySQLServerVersion() string 80 // SetMySQLServerVersion sets the runtime MySQL server version. 81 SetMySQLServerVersion(version string) 82 // GetAWS returns the database AWS metadata. 83 GetAWS() AWS 84 // SetStatusAWS sets the database AWS metadata in the status field. 85 SetStatusAWS(AWS) 86 // SetAWSExternalID sets the database AWS external ID in the Spec.AWS field. 87 SetAWSExternalID(id string) 88 // SetAWSAssumeRole sets the database AWS assume role arn in the Spec.AWS field. 89 SetAWSAssumeRole(roleARN string) 90 // GetGCP returns GCP information for Cloud SQL databases. 91 GetGCP() GCPCloudSQL 92 // GetAzure returns Azure database server metadata. 93 GetAzure() Azure 94 // SetStatusAzure sets the database Azure metadata in the status field. 95 SetStatusAzure(Azure) 96 // GetAD returns Active Directory database configuration. 97 GetAD() AD 98 // GetType returns the database authentication type: self-hosted, RDS, Redshift or Cloud SQL. 99 GetType() string 100 // GetSecretStore returns secret store configurations. 101 GetSecretStore() SecretStore 102 // GetManagedUsers returns a list of database users that are managed by Teleport. 103 GetManagedUsers() []string 104 // SetManagedUsers sets a list of database users that are managed by Teleport. 105 SetManagedUsers(users []string) 106 // GetMongoAtlas returns Mongo Atlas database metadata. 107 GetMongoAtlas() MongoAtlas 108 // IsRDS returns true if this is an RDS/Aurora database. 109 IsRDS() bool 110 // IsRDSProxy returns true if this is an RDS Proxy database. 111 IsRDSProxy() bool 112 // IsRedshift returns true if this is a Redshift database. 113 IsRedshift() bool 114 // IsCloudSQL returns true if this is a Cloud SQL database. 115 IsCloudSQL() bool 116 // IsAzure returns true if this is an Azure database. 117 IsAzure() bool 118 // IsElastiCache returns true if this is an AWS ElastiCache database. 119 IsElastiCache() bool 120 // IsMemoryDB returns true if this is an AWS MemoryDB database. 121 IsMemoryDB() bool 122 // IsAWSHosted returns true if database is hosted by AWS. 123 IsAWSHosted() bool 124 // IsCloudHosted returns true if database is hosted in the cloud (AWS, Azure or Cloud SQL). 125 IsCloudHosted() bool 126 // RequireAWSIAMRolesAsUsers returns true for database types that require 127 // AWS IAM roles as database users. 128 RequireAWSIAMRolesAsUsers() bool 129 // SupportAWSIAMRoleARNAsUsers returns true for database types that support 130 // AWS IAM roles as database users. 131 SupportAWSIAMRoleARNAsUsers() bool 132 // Copy returns a copy of this database resource. 133 Copy() *DatabaseV3 134 // GetAdminUser returns database privileged user information. 135 GetAdminUser() DatabaseAdminUser 136 // SupportsAutoUsers returns true if this database supports automatic 137 // user provisioning. 138 SupportsAutoUsers() bool 139 // GetEndpointType returns the endpoint type of the database, if available. 140 GetEndpointType() string 141 // GetCloud gets the cloud this database is running on, or an empty string if it 142 // isn't running on a cloud provider. 143 GetCloud() string 144 } 145 146 // NewDatabaseV3 creates a new database resource. 147 func NewDatabaseV3(meta Metadata, spec DatabaseSpecV3) (*DatabaseV3, error) { 148 database := &DatabaseV3{ 149 Metadata: meta, 150 Spec: spec, 151 } 152 if err := database.CheckAndSetDefaults(); err != nil { 153 return nil, trace.Wrap(err) 154 } 155 return database, nil 156 } 157 158 // GetVersion returns the database resource version. 159 func (d *DatabaseV3) GetVersion() string { 160 return d.Version 161 } 162 163 // GetKind returns the database resource kind. 164 func (d *DatabaseV3) GetKind() string { 165 return d.Kind 166 } 167 168 // GetSubKind returns the database resource subkind. 169 func (d *DatabaseV3) GetSubKind() string { 170 return d.SubKind 171 } 172 173 // SetSubKind sets the database resource subkind. 174 func (d *DatabaseV3) SetSubKind(sk string) { 175 d.SubKind = sk 176 } 177 178 // GetResourceID returns the database resource ID. 179 func (d *DatabaseV3) GetResourceID() int64 { 180 return d.Metadata.ID 181 } 182 183 // SetResourceID sets the database resource ID. 184 func (d *DatabaseV3) SetResourceID(id int64) { 185 d.Metadata.ID = id 186 } 187 188 // GetRevision returns the revision 189 func (d *DatabaseV3) GetRevision() string { 190 return d.Metadata.GetRevision() 191 } 192 193 // SetRevision sets the revision 194 func (d *DatabaseV3) SetRevision(rev string) { 195 d.Metadata.SetRevision(rev) 196 } 197 198 // GetMetadata returns the database resource metadata. 199 func (d *DatabaseV3) GetMetadata() Metadata { 200 return d.Metadata 201 } 202 203 // Origin returns the origin value of the resource. 204 func (d *DatabaseV3) Origin() string { 205 return d.Metadata.Origin() 206 } 207 208 // SetOrigin sets the origin value of the resource. 209 func (d *DatabaseV3) SetOrigin(origin string) { 210 d.Metadata.SetOrigin(origin) 211 } 212 213 // GetNamespace returns the database resource namespace. 214 func (d *DatabaseV3) GetNamespace() string { 215 return d.Metadata.Namespace 216 } 217 218 // SetExpiry sets the database resource expiration time. 219 func (d *DatabaseV3) SetExpiry(expiry time.Time) { 220 d.Metadata.SetExpiry(expiry) 221 } 222 223 // Expiry returns the database resource expiration time. 224 func (d *DatabaseV3) Expiry() time.Time { 225 return d.Metadata.Expiry() 226 } 227 228 // GetName returns the database resource name. 229 func (d *DatabaseV3) GetName() string { 230 return d.Metadata.Name 231 } 232 233 // SetName sets the database resource name. 234 func (d *DatabaseV3) SetName(name string) { 235 d.Metadata.Name = name 236 } 237 238 // GetStaticLabels returns the database static labels. 239 func (d *DatabaseV3) GetStaticLabels() map[string]string { 240 return d.Metadata.Labels 241 } 242 243 // SetStaticLabels sets the database static labels. 244 func (d *DatabaseV3) SetStaticLabels(sl map[string]string) { 245 d.Metadata.Labels = sl 246 } 247 248 // GetDynamicLabels returns the database dynamic labels. 249 func (d *DatabaseV3) GetDynamicLabels() map[string]CommandLabel { 250 if d.Spec.DynamicLabels == nil { 251 return nil 252 } 253 return V2ToLabels(d.Spec.DynamicLabels) 254 } 255 256 // SetDynamicLabels sets the database dynamic labels 257 func (d *DatabaseV3) SetDynamicLabels(dl map[string]CommandLabel) { 258 d.Spec.DynamicLabels = LabelsToV2(dl) 259 } 260 261 // GetLabel retrieves the label with the provided key. If not found 262 // value will be empty and ok will be false. 263 func (d *DatabaseV3) GetLabel(key string) (value string, ok bool) { 264 if cmd, ok := d.Spec.DynamicLabels[key]; ok { 265 return cmd.Result, ok 266 } 267 268 v, ok := d.Metadata.Labels[key] 269 return v, ok 270 } 271 272 // GetAllLabels returns the database combined static and dynamic labels. 273 func (d *DatabaseV3) GetAllLabels() map[string]string { 274 return CombineLabels(d.Metadata.Labels, d.Spec.DynamicLabels) 275 } 276 277 // GetDescription returns the database description. 278 func (d *DatabaseV3) GetDescription() string { 279 return d.Metadata.Description 280 } 281 282 // GetProtocol returns the database protocol. 283 func (d *DatabaseV3) GetProtocol() string { 284 return d.Spec.Protocol 285 } 286 287 // GetURI returns the database connection address. 288 func (d *DatabaseV3) GetURI() string { 289 return d.Spec.URI 290 } 291 292 // SetURI sets the database connection address. 293 func (d *DatabaseV3) SetURI(uri string) { 294 d.Spec.URI = uri 295 } 296 297 // GetAdminUser returns database privileged user information. 298 func (d *DatabaseV3) GetAdminUser() (ret DatabaseAdminUser) { 299 // First check the spec. 300 if d.Spec.AdminUser != nil { 301 ret = *d.Spec.AdminUser 302 } 303 304 // If it's not in the spec, check labels (for auto-discovered databases). 305 // TODO Azure will require different labels. 306 if d.Origin() == OriginCloud { 307 if ret.Name == "" { 308 ret.Name = d.Metadata.Labels[DatabaseAdminLabel] 309 } 310 if ret.DefaultDatabase == "" { 311 ret.DefaultDatabase = d.Metadata.Labels[DatabaseAdminDefaultDatabaseLabel] 312 } 313 } 314 return 315 } 316 317 // GetOracle returns the Oracle options from spec. 318 func (d *DatabaseV3) GetOracle() OracleOptions { 319 return d.Spec.Oracle 320 } 321 322 // SupportsAutoUsers returns true if this database supports automatic user 323 // provisioning. 324 func (d *DatabaseV3) SupportsAutoUsers() bool { 325 switch d.GetProtocol() { 326 case DatabaseProtocolPostgreSQL: 327 switch d.GetType() { 328 case DatabaseTypeSelfHosted, DatabaseTypeRDS, DatabaseTypeRedshift: 329 return true 330 } 331 case DatabaseProtocolMySQL: 332 switch d.GetType() { 333 case DatabaseTypeSelfHosted, DatabaseTypeRDS: 334 return true 335 } 336 337 case DatabaseProtocolMongoDB: 338 switch d.GetType() { 339 case DatabaseTypeSelfHosted: 340 return true 341 } 342 } 343 return false 344 } 345 346 // GetCA returns the database CA certificate. If more than one CA is set, then 347 // the user provided CA is returned first (Spec field). 348 // Auto-downloaded CA certificate is returned otherwise. 349 func (d *DatabaseV3) GetCA() string { 350 if d.Spec.TLS.CACert != "" { 351 return d.Spec.TLS.CACert 352 } 353 if d.Spec.CACert != "" { 354 return d.Spec.CACert 355 } 356 return d.Status.CACert 357 } 358 359 // SetCA sets the database CA certificate in the Spec.TLS.CACert field. 360 func (d *DatabaseV3) SetCA(caCert string) { 361 d.Spec.TLS.CACert = caCert 362 } 363 364 // GetTLS returns Database TLS configuration. 365 func (d *DatabaseV3) GetTLS() DatabaseTLS { 366 return d.Spec.TLS 367 } 368 369 // SetStatusCA sets the database CA certificate in the status field. 370 func (d *DatabaseV3) SetStatusCA(ca string) { 371 d.Status.CACert = ca 372 } 373 374 // GetStatusCA gets the database CA certificate in the status field. 375 func (d *DatabaseV3) GetStatusCA() string { 376 return d.Status.CACert 377 } 378 379 // GetMySQL returns the MySQL options from spec. 380 func (d *DatabaseV3) GetMySQL() MySQLOptions { 381 return d.Spec.MySQL 382 } 383 384 // GetMySQLServerVersion returns the MySQL server version reported by the database or the value from configuration 385 // if the first one is not available. 386 func (d *DatabaseV3) GetMySQLServerVersion() string { 387 if d.Status.MySQL.ServerVersion != "" { 388 return d.Status.MySQL.ServerVersion 389 } 390 391 return d.Spec.MySQL.ServerVersion 392 } 393 394 // SetMySQLServerVersion sets the runtime MySQL server version. 395 func (d *DatabaseV3) SetMySQLServerVersion(version string) { 396 d.Status.MySQL.ServerVersion = version 397 } 398 399 // IsEmpty returns true if AWS metadata is empty. 400 func (a AWS) IsEmpty() bool { 401 return protoKnownFieldsEqual(&a, &AWS{}) 402 } 403 404 // Partition returns the AWS partition based on the region. 405 func (a AWS) Partition() string { 406 return awsutils.GetPartitionFromRegion(a.Region) 407 } 408 409 // GetAWS returns the database AWS metadata. 410 func (d *DatabaseV3) GetAWS() AWS { 411 if !d.Status.AWS.IsEmpty() { 412 return d.Status.AWS 413 } 414 return d.Spec.AWS 415 } 416 417 // SetStatusAWS sets the database AWS metadata in the status field. 418 func (d *DatabaseV3) SetStatusAWS(aws AWS) { 419 d.Status.AWS = aws 420 } 421 422 // SetAWSExternalID sets the database AWS external ID in the Spec.AWS field. 423 func (d *DatabaseV3) SetAWSExternalID(id string) { 424 d.Spec.AWS.ExternalID = id 425 } 426 427 // SetAWSAssumeRole sets the database AWS assume role arn in the Spec.AWS field. 428 func (d *DatabaseV3) SetAWSAssumeRole(roleARN string) { 429 d.Spec.AWS.AssumeRoleARN = roleARN 430 } 431 432 // GetGCP returns GCP information for Cloud SQL databases. 433 func (d *DatabaseV3) GetGCP() GCPCloudSQL { 434 return d.Spec.GCP 435 } 436 437 // IsEmpty returns true if Azure metadata is empty. 438 func (a Azure) IsEmpty() bool { 439 return protoKnownFieldsEqual(&a, &Azure{}) 440 } 441 442 // GetAzure returns Azure database server metadata. 443 func (d *DatabaseV3) GetAzure() Azure { 444 if !d.Status.Azure.IsEmpty() { 445 return d.Status.Azure 446 } 447 return d.Spec.Azure 448 } 449 450 // SetStatusAzure sets the database Azure metadata in the status field. 451 func (d *DatabaseV3) SetStatusAzure(azure Azure) { 452 d.Status.Azure = azure 453 } 454 455 // GetAD returns Active Directory database configuration. 456 func (d *DatabaseV3) GetAD() AD { 457 return d.Spec.AD 458 } 459 460 // IsRDS returns true if this is an AWS RDS/Aurora instance. 461 func (d *DatabaseV3) IsRDS() bool { 462 return d.GetType() == DatabaseTypeRDS 463 } 464 465 // IsRDSProxy returns true if this is an AWS RDS Proxy database. 466 func (d *DatabaseV3) IsRDSProxy() bool { 467 return d.GetType() == DatabaseTypeRDSProxy 468 } 469 470 // IsRedshift returns true if this is a Redshift database instance. 471 func (d *DatabaseV3) IsRedshift() bool { 472 return d.GetType() == DatabaseTypeRedshift 473 } 474 475 // IsCloudSQL returns true if this database is a Cloud SQL instance. 476 func (d *DatabaseV3) IsCloudSQL() bool { 477 return d.GetType() == DatabaseTypeCloudSQL 478 } 479 480 // IsAzure returns true if this is Azure hosted database. 481 func (d *DatabaseV3) IsAzure() bool { 482 return d.GetType() == DatabaseTypeAzure 483 } 484 485 // IsElastiCache returns true if this is an AWS ElastiCache database. 486 func (d *DatabaseV3) IsElastiCache() bool { 487 return d.GetType() == DatabaseTypeElastiCache 488 } 489 490 // IsMemoryDB returns true if this is an AWS MemoryDB database. 491 func (d *DatabaseV3) IsMemoryDB() bool { 492 return d.GetType() == DatabaseTypeMemoryDB 493 } 494 495 // IsAWSKeyspaces returns true if this is an AWS hosted Cassandra database. 496 func (d *DatabaseV3) IsAWSKeyspaces() bool { 497 return d.GetType() == DatabaseTypeAWSKeyspaces 498 } 499 500 // IsDynamoDB returns true if this is an AWS hosted DynamoDB database. 501 func (d *DatabaseV3) IsDynamoDB() bool { 502 return d.GetType() == DatabaseTypeDynamoDB 503 } 504 505 // IsOpenSearch returns true if this is an AWS hosted OpenSearch instance. 506 func (d *DatabaseV3) IsOpenSearch() bool { 507 return d.GetType() == DatabaseTypeOpenSearch 508 } 509 510 // IsAWSHosted returns true if database is hosted by AWS. 511 func (d *DatabaseV3) IsAWSHosted() bool { 512 _, ok := d.getAWSType() 513 return ok 514 } 515 516 // IsCloudHosted returns true if database is hosted in the cloud (AWS, Azure or 517 // Cloud SQL). 518 func (d *DatabaseV3) IsCloudHosted() bool { 519 return d.IsAWSHosted() || d.IsCloudSQL() || d.IsAzure() 520 } 521 522 // GetCloud gets the cloud this database is running on, or an empty string if it 523 // isn't running on a cloud provider. 524 func (d *DatabaseV3) GetCloud() string { 525 switch { 526 case d.IsAWSHosted(): 527 return CloudAWS 528 case d.IsCloudSQL(): 529 return CloudGCP 530 case d.IsAzure(): 531 return CloudAzure 532 default: 533 return "" 534 } 535 } 536 537 // getAWSType returns the database type. 538 func (d *DatabaseV3) getAWSType() (string, bool) { 539 aws := d.GetAWS() 540 switch d.Spec.Protocol { 541 case DatabaseTypeCassandra: 542 if !aws.IsEmpty() { 543 return DatabaseTypeAWSKeyspaces, true 544 } 545 case DatabaseTypeDynamoDB: 546 return DatabaseTypeDynamoDB, true 547 case DatabaseTypeOpenSearch: 548 return DatabaseTypeOpenSearch, true 549 } 550 if aws.Redshift.ClusterID != "" { 551 return DatabaseTypeRedshift, true 552 } 553 if aws.RedshiftServerless.WorkgroupName != "" || aws.RedshiftServerless.EndpointName != "" { 554 return DatabaseTypeRedshiftServerless, true 555 } 556 if aws.ElastiCache.ReplicationGroupID != "" { 557 return DatabaseTypeElastiCache, true 558 } 559 if aws.MemoryDB.ClusterName != "" { 560 return DatabaseTypeMemoryDB, true 561 } 562 if aws.RDSProxy.Name != "" || aws.RDSProxy.CustomEndpointName != "" { 563 return DatabaseTypeRDSProxy, true 564 } 565 if aws.Region != "" || aws.RDS.InstanceID != "" || aws.RDS.ResourceID != "" || aws.RDS.ClusterID != "" { 566 return DatabaseTypeRDS, true 567 } 568 return "", false 569 } 570 571 // GetType returns the database type. 572 func (d *DatabaseV3) GetType() string { 573 if d.GetMongoAtlas().Name != "" { 574 return DatabaseTypeMongoAtlas 575 } 576 577 if awsType, ok := d.getAWSType(); ok { 578 return awsType 579 } 580 581 if d.GetGCP().ProjectID != "" { 582 return DatabaseTypeCloudSQL 583 } 584 if d.GetAzure().Name != "" { 585 return DatabaseTypeAzure 586 } 587 588 return DatabaseTypeSelfHosted 589 } 590 591 // String returns the database string representation. 592 func (d *DatabaseV3) String() string { 593 return fmt.Sprintf("Database(Name=%v, Type=%v, Labels=%v)", 594 d.GetName(), d.GetType(), d.GetAllLabels()) 595 } 596 597 // Copy returns a copy of this database resource. 598 func (d *DatabaseV3) Copy() *DatabaseV3 { 599 return utils.CloneProtoMsg(d) 600 } 601 602 // MatchSearch goes through select field values and tries to 603 // match against the list of search values. 604 func (d *DatabaseV3) MatchSearch(values []string) bool { 605 fieldVals := append(utils.MapToStrings(d.GetAllLabels()), d.GetName(), d.GetDescription(), d.GetProtocol(), d.GetType()) 606 607 var custom func(string) bool 608 switch d.GetType() { 609 case DatabaseTypeCloudSQL: 610 custom = func(val string) bool { 611 return strings.EqualFold(val, "cloud") || strings.EqualFold(val, "cloud sql") 612 } 613 } 614 615 return MatchSearch(fieldVals, values, custom) 616 } 617 618 // setStaticFields sets static resource header and metadata fields. 619 func (d *DatabaseV3) setStaticFields() { 620 d.Kind = KindDatabase 621 d.Version = V3 622 } 623 624 // validDatabaseNameRegexp filters the allowed characters in database names. 625 // This is the (almost) the same regexp used to check for valid DNS 1035 labels, 626 // except we allow uppercase chars. 627 var validDatabaseNameRegexp = regexp.MustCompile(`^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$`) 628 629 // ValidateDatabaseName returns an error if a given string is not a valid 630 // Database name. 631 // Unlike application access proxy, database name doesn't necessarily 632 // need to be a valid subdomain but use the same validation logic for the 633 // simplicity and consistency, except two differences: don't restrict names to 634 // 63 chars in length and allow upper case chars. 635 func ValidateDatabaseName(name string) error { 636 return ValidateResourceName(validDatabaseNameRegexp, name) 637 } 638 639 // CheckAndSetDefaults checks and sets default values for any missing fields. 640 func (d *DatabaseV3) CheckAndSetDefaults() error { 641 d.setStaticFields() 642 if err := d.Metadata.CheckAndSetDefaults(); err != nil { 643 return trace.Wrap(err) 644 } 645 646 if err := ValidateDatabaseName(d.GetName()); err != nil { 647 return trace.Wrap(err, "invalid database name") 648 } 649 650 for key := range d.Spec.DynamicLabels { 651 if !IsValidLabelKey(key) { 652 return trace.BadParameter("database %q invalid label key: %q", d.GetName(), key) 653 } 654 } 655 if d.Spec.Protocol == "" { 656 return trace.BadParameter("database %q protocol is empty", d.GetName()) 657 } 658 if d.Spec.URI == "" { 659 switch d.GetType() { 660 case DatabaseTypeAWSKeyspaces: 661 if d.Spec.AWS.Region != "" { 662 // In case of AWS Hosted Cassandra allow to omit URI. 663 // The URL will be constructed from the database resource based on the region and account ID. 664 d.Spec.URI = awsutils.CassandraEndpointURLForRegion(d.Spec.AWS.Region) 665 } else { 666 return trace.BadParameter("AWS Keyspaces database %q URI is empty and cannot be derived without a configured AWS region", 667 d.GetName()) 668 } 669 case DatabaseTypeDynamoDB: 670 if d.Spec.AWS.Region != "" { 671 d.Spec.URI = awsutils.DynamoDBURIForRegion(d.Spec.AWS.Region) 672 } else { 673 return trace.BadParameter("DynamoDB database %q URI is empty and cannot be derived without a configured AWS region", 674 d.GetName()) 675 } 676 default: 677 return trace.BadParameter("database %q URI is empty", d.GetName()) 678 } 679 } 680 if d.Spec.MySQL.ServerVersion != "" && d.Spec.Protocol != "mysql" { 681 return trace.BadParameter("database %q MySQL ServerVersion can be only set for MySQL database", 682 d.GetName()) 683 } 684 685 // In case of RDS, Aurora or Redshift, AWS information such as region or 686 // cluster ID can be extracted from the endpoint if not provided. 687 switch { 688 case d.IsDynamoDB(): 689 if err := d.handleDynamoDBConfig(); err != nil { 690 return trace.Wrap(err) 691 } 692 case d.IsOpenSearch(): 693 if err := d.handleOpenSearchConfig(); err != nil { 694 return trace.Wrap(err) 695 } 696 case awsutils.IsRDSEndpoint(d.Spec.URI): 697 details, err := awsutils.ParseRDSEndpoint(d.Spec.URI) 698 if err != nil { 699 slog.WarnContext(context.Background(), "Failed to parse RDS endpoint.", "uri", d.Spec.URI, "error", err) 700 break 701 } 702 if d.Spec.AWS.RDS.InstanceID == "" { 703 d.Spec.AWS.RDS.InstanceID = details.InstanceID 704 } 705 if d.Spec.AWS.RDS.ClusterID == "" { 706 d.Spec.AWS.RDS.ClusterID = details.ClusterID 707 } 708 if d.Spec.AWS.RDSProxy.Name == "" { 709 d.Spec.AWS.RDSProxy.Name = details.ProxyName 710 } 711 if d.Spec.AWS.RDSProxy.CustomEndpointName == "" { 712 d.Spec.AWS.RDSProxy.CustomEndpointName = details.ProxyCustomEndpointName 713 } 714 if d.Spec.AWS.Region == "" { 715 d.Spec.AWS.Region = details.Region 716 } 717 if details.ClusterCustomEndpointName != "" && d.Spec.AWS.RDS.ClusterID == "" { 718 return trace.BadParameter("database %q missing RDS ClusterID for RDS Aurora custom endpoint %v", 719 d.GetName(), d.Spec.URI) 720 } 721 case awsutils.IsRedshiftEndpoint(d.Spec.URI): 722 clusterID, region, err := awsutils.ParseRedshiftEndpoint(d.Spec.URI) 723 if err != nil { 724 return trace.Wrap(err) 725 } 726 if d.Spec.AWS.Redshift.ClusterID == "" { 727 d.Spec.AWS.Redshift.ClusterID = clusterID 728 } 729 if d.Spec.AWS.Region == "" { 730 d.Spec.AWS.Region = region 731 } 732 case awsutils.IsRedshiftServerlessEndpoint(d.Spec.URI): 733 details, err := awsutils.ParseRedshiftServerlessEndpoint(d.Spec.URI) 734 if err != nil { 735 slog.WarnContext(context.Background(), "Failed to parse Redshift Serverless endpoint.", "uri", d.Spec.URI, "error", err) 736 break 737 } 738 if d.Spec.AWS.RedshiftServerless.WorkgroupName == "" { 739 d.Spec.AWS.RedshiftServerless.WorkgroupName = details.WorkgroupName 740 } 741 if d.Spec.AWS.RedshiftServerless.EndpointName == "" { 742 d.Spec.AWS.RedshiftServerless.EndpointName = details.EndpointName 743 } 744 if d.Spec.AWS.AccountID == "" { 745 d.Spec.AWS.AccountID = details.AccountID 746 } 747 if d.Spec.AWS.Region == "" { 748 d.Spec.AWS.Region = details.Region 749 } 750 case awsutils.IsElastiCacheEndpoint(d.Spec.URI): 751 endpointInfo, err := awsutils.ParseElastiCacheEndpoint(d.Spec.URI) 752 if err != nil { 753 slog.WarnContext(context.Background(), "Failed to parse ElastiCache endpoint", "uri", d.Spec.URI, "error", err) 754 break 755 } 756 if d.Spec.AWS.ElastiCache.ReplicationGroupID == "" { 757 d.Spec.AWS.ElastiCache.ReplicationGroupID = endpointInfo.ID 758 } 759 if d.Spec.AWS.Region == "" { 760 d.Spec.AWS.Region = endpointInfo.Region 761 } 762 d.Spec.AWS.ElastiCache.TransitEncryptionEnabled = endpointInfo.TransitEncryptionEnabled 763 d.Spec.AWS.ElastiCache.EndpointType = endpointInfo.EndpointType 764 case awsutils.IsMemoryDBEndpoint(d.Spec.URI): 765 endpointInfo, err := awsutils.ParseMemoryDBEndpoint(d.Spec.URI) 766 if err != nil { 767 slog.WarnContext(context.Background(), "Failed to parse MemoryDB endpoint", "uri", d.Spec.URI, "error", err) 768 break 769 } 770 if d.Spec.AWS.MemoryDB.ClusterName == "" { 771 d.Spec.AWS.MemoryDB.ClusterName = endpointInfo.ID 772 } 773 if d.Spec.AWS.Region == "" { 774 d.Spec.AWS.Region = endpointInfo.Region 775 } 776 d.Spec.AWS.MemoryDB.TLSEnabled = endpointInfo.TransitEncryptionEnabled 777 d.Spec.AWS.MemoryDB.EndpointType = endpointInfo.EndpointType 778 779 case azureutils.IsDatabaseEndpoint(d.Spec.URI): 780 // For Azure MySQL and PostgresSQL. 781 name, err := azureutils.ParseDatabaseEndpoint(d.Spec.URI) 782 if err != nil { 783 return trace.Wrap(err) 784 } 785 if d.Spec.Azure.Name == "" { 786 d.Spec.Azure.Name = name 787 } 788 case awsutils.IsKeyspacesEndpoint(d.Spec.URI): 789 if d.Spec.AWS.AccountID == "" { 790 return trace.BadParameter("database %q AWS account ID is empty", 791 d.GetName()) 792 } 793 if d.Spec.AWS.Region == "" { 794 switch { 795 case d.IsAWSKeyspaces(): 796 region, err := awsutils.CassandraEndpointRegion(d.Spec.URI) 797 if err != nil { 798 return trace.Wrap(err) 799 } 800 d.Spec.AWS.Region = region 801 default: 802 return trace.BadParameter("database %q AWS region is empty", 803 d.GetName()) 804 } 805 } 806 case azureutils.IsCacheForRedisEndpoint(d.Spec.URI): 807 // ResourceID is required for fetching Redis tokens. 808 if d.Spec.Azure.ResourceID == "" { 809 return trace.BadParameter("database %q Azure resource ID is empty", 810 d.GetName()) 811 } 812 813 name, err := azureutils.ParseCacheForRedisEndpoint(d.Spec.URI) 814 if err != nil { 815 return trace.Wrap(err) 816 } 817 818 if d.Spec.Azure.Name == "" { 819 d.Spec.Azure.Name = name 820 } 821 case azureutils.IsMSSQLServerEndpoint(d.Spec.URI): 822 if d.Spec.Azure.Name == "" { 823 name, err := azureutils.ParseMSSQLEndpoint(d.Spec.URI) 824 if err != nil { 825 return trace.Wrap(err) 826 } 827 d.Spec.Azure.Name = name 828 } 829 case atlasutils.IsAtlasEndpoint(d.Spec.URI): 830 name, err := atlasutils.ParseAtlasEndpoint(d.Spec.URI) 831 if err != nil { 832 return trace.Wrap(err) 833 } 834 d.Spec.MongoAtlas.Name = name 835 } 836 837 // Validate AWS Specific configuration 838 if d.Spec.AWS.AccountID != "" { 839 if err := awsutils.IsValidAccountID(d.Spec.AWS.AccountID); err != nil { 840 return trace.BadParameter("database %q has invalid AWS account ID: %v", 841 d.GetName(), err) 842 } 843 } 844 845 if d.Spec.AWS.ExternalID != "" && d.Spec.AWS.AssumeRoleARN == "" && !d.RequireAWSIAMRolesAsUsers() { 846 // Databases that use database username to assume an IAM role do not 847 // need assume_role_arn in configuration when external_id is set. 848 return trace.BadParameter("AWS database %q has external_id %q, but assume_role_arn is empty", 849 d.GetName(), d.Spec.AWS.ExternalID) 850 } 851 852 // Validate Cloud SQL specific configuration. 853 switch { 854 case d.Spec.GCP.ProjectID != "" && d.Spec.GCP.InstanceID == "": 855 return trace.BadParameter("database %q missing Cloud SQL instance ID", 856 d.GetName()) 857 case d.Spec.GCP.ProjectID == "" && d.Spec.GCP.InstanceID != "": 858 return trace.BadParameter("database %q missing Cloud SQL project ID", 859 d.GetName()) 860 } 861 862 // Admin user should only be specified for databases that support automatic 863 // user provisioning. 864 if d.GetAdminUser().Name != "" && !d.SupportsAutoUsers() { 865 return trace.BadParameter("cannot set admin user on database %q: %v/%v databases don't support automatic user provisioning yet", 866 d.GetName(), d.GetProtocol(), d.GetType()) 867 } 868 869 switch protocol := d.GetProtocol(); protocol { 870 case DatabaseProtocolClickHouseHTTP, DatabaseProtocolClickHouse: 871 const ( 872 clickhouseNativeSchema = "clickhouse" 873 clickhouseHTTPSchema = "https" 874 ) 875 parts := strings.Split(d.GetURI(), ":") 876 if len(parts) == 3 { 877 break 878 } else if len(parts) != 2 { 879 return trace.BadParameter("invalid ClickHouse URL %s", d.GetURI()) 880 } 881 882 if !strings.HasPrefix(d.Spec.URI, clickhouseHTTPSchema) && protocol == DatabaseProtocolClickHouseHTTP { 883 d.Spec.URI = fmt.Sprintf("%s://%s", clickhouseHTTPSchema, d.Spec.URI) 884 } 885 if protocol == DatabaseProtocolClickHouse { 886 d.Spec.URI = fmt.Sprintf("%s://%s", clickhouseNativeSchema, d.Spec.URI) 887 } 888 } 889 890 return nil 891 } 892 893 // IsEqual determines if two database resources are equivalent to one another. 894 func (d *DatabaseV3) IsEqual(i Database) bool { 895 if other, ok := i.(*DatabaseV3); ok { 896 return deriveTeleportEqualDatabaseV3(d, other) 897 } 898 return false 899 } 900 901 // handleDynamoDBConfig handles DynamoDB configuration checking. 902 func (d *DatabaseV3) handleDynamoDBConfig() error { 903 if d.Spec.AWS.AccountID == "" { 904 return trace.BadParameter("database %q AWS account ID is empty", d.GetName()) 905 } 906 907 info, err := awsutils.ParseDynamoDBEndpoint(d.Spec.URI) 908 switch { 909 case err != nil: 910 // when region parsing returns an error but the region is set, it's ok because we can just construct the URI using the region, 911 // so we check if the region is configured to see if this is really a configuration error. 912 if d.Spec.AWS.Region == "" { 913 // the AWS region is empty and we can't derive it from the URI, so this is a config error. 914 return trace.BadParameter("database %q AWS region is empty and cannot be derived from the URI %q", 915 d.GetName(), d.Spec.URI) 916 } 917 if awsutils.IsAWSEndpoint(d.Spec.URI) { 918 // The user configured an AWS URI that doesn't look like a DynamoDB endpoint. 919 // The URI must look like <service>.<region>.<partition> or <region>.<partition> 920 return trace.Wrap(err) 921 } 922 case d.Spec.AWS.Region == "": 923 // if the AWS region is empty we can just use the region extracted from the URI. 924 d.Spec.AWS.Region = info.Region 925 case d.Spec.AWS.Region != info.Region: 926 // if the AWS region is not empty but doesn't match the URI, this may indicate a user configuration mistake. 927 return trace.BadParameter("database %q AWS region %q does not match the configured URI region %q,"+ 928 " omit the URI and it will be derived automatically for the configured AWS region", 929 d.GetName(), d.Spec.AWS.Region, info.Region) 930 } 931 932 if d.Spec.URI == "" { 933 d.Spec.URI = awsutils.DynamoDBURIForRegion(d.Spec.AWS.Region) 934 } 935 return nil 936 } 937 938 // handleOpenSearchConfig handles OpenSearch configuration checks. 939 func (d *DatabaseV3) handleOpenSearchConfig() error { 940 if d.Spec.AWS.AccountID == "" { 941 return trace.BadParameter("database %q AWS account ID is empty", d.GetName()) 942 } 943 944 info, err := awsutils.ParseOpensearchEndpoint(d.Spec.URI) 945 switch { 946 case err != nil: 947 // parsing the endpoint can return an error, especially if the custom endpoint feature is in use. 948 // this is fine as long as we have the region explicitly configured. 949 if d.Spec.AWS.Region == "" { 950 // the AWS region is empty, and we can't derive it from the URI, so this is a config error. 951 return trace.BadParameter("database %q AWS region is missing and cannot be derived from the URI %q", 952 d.GetName(), d.Spec.URI) 953 } 954 if awsutils.IsAWSEndpoint(d.Spec.URI) { 955 // The user configured an AWS URI that doesn't look like a OpenSearch endpoint. 956 // The URI must look like: <region>.<service>.<partition>. 957 return trace.Wrap(err) 958 } 959 case d.Spec.AWS.Region == "": 960 // if the AWS region is empty we can just use the region extracted from the URI. 961 d.Spec.AWS.Region = info.Region 962 case d.Spec.AWS.Region != info.Region: 963 // if the AWS region is not empty but doesn't match the URI, this may indicate a user configuration mistake. 964 return trace.BadParameter("database %q AWS region %q does not match the configured URI region %q,"+ 965 " omit the URI and it will be derived automatically for the configured AWS region", 966 d.GetName(), d.Spec.AWS.Region, info.Region) 967 } 968 969 return nil 970 } 971 972 // GetSecretStore returns secret store configurations. 973 func (d *DatabaseV3) GetSecretStore() SecretStore { 974 return d.Spec.AWS.SecretStore 975 } 976 977 // GetManagedUsers returns a list of database users that are managed by Teleport. 978 func (d *DatabaseV3) GetManagedUsers() []string { 979 return d.Status.ManagedUsers 980 } 981 982 // SetManagedUsers sets a list of database users that are managed by Teleport. 983 func (d *DatabaseV3) SetManagedUsers(users []string) { 984 d.Status.ManagedUsers = users 985 } 986 987 // GetMongoAtlas returns Mongo Atlas database metadata. 988 func (d *DatabaseV3) GetMongoAtlas() MongoAtlas { 989 return d.Spec.MongoAtlas 990 } 991 992 // RequireAWSIAMRolesAsUsers returns true for database types that require AWS 993 // IAM roles as database users. 994 // IMPORTANT: if you add a database that requires AWS IAM Roles as users, 995 // and that database supports discovery, be sure to update RequireAWSIAMRolesAsUsersMatchers 996 // in matchers_aws.go as well. 997 func (d *DatabaseV3) RequireAWSIAMRolesAsUsers() bool { 998 awsType, ok := d.getAWSType() 999 if !ok { 1000 return false 1001 } 1002 1003 switch awsType { 1004 case DatabaseTypeAWSKeyspaces, 1005 DatabaseTypeDynamoDB, 1006 DatabaseTypeOpenSearch, 1007 DatabaseTypeRedshiftServerless: 1008 return true 1009 default: 1010 return false 1011 } 1012 } 1013 1014 // SupportAWSIAMRoleARNAsUsers returns true for database types that support AWS 1015 // IAM roles as database users. 1016 func (d *DatabaseV3) SupportAWSIAMRoleARNAsUsers() bool { 1017 switch d.GetType() { 1018 // Note that databases in this list use IAM auth when: 1019 // - the database user is a full AWS role ARN role 1020 // - or the database user starts with "role/" 1021 // 1022 // Other database users will fallback to default auth methods (e.g X.509 for 1023 // MongoAtlas, regular auth token for Redshift). 1024 // 1025 // Therefore it is important to make sure "/" is an invalid character for 1026 // regular in-database usernames so that "role/" can be differentiated from 1027 // regular usernames. 1028 case DatabaseTypeMongoAtlas, 1029 DatabaseTypeRedshift: 1030 return true 1031 default: 1032 return false 1033 } 1034 } 1035 1036 // GetEndpointType returns the endpoint type of the database, if available. 1037 func (d *DatabaseV3) GetEndpointType() string { 1038 if endpointType, ok := d.GetStaticLabels()[DiscoveryLabelEndpointType]; ok { 1039 return endpointType 1040 } 1041 switch d.GetType() { 1042 case DatabaseTypeElastiCache: 1043 return d.GetAWS().ElastiCache.EndpointType 1044 case DatabaseTypeMemoryDB: 1045 return d.GetAWS().MemoryDB.EndpointType 1046 case DatabaseTypeOpenSearch: 1047 return d.GetAWS().OpenSearch.EndpointType 1048 case DatabaseTypeRDS: 1049 // If not available from discovery tags, get the endpoint type from the 1050 // URL. 1051 if details, err := awsutils.ParseRDSEndpoint(d.GetURI()); err == nil { 1052 return details.EndpointType 1053 } 1054 } 1055 return "" 1056 } 1057 1058 const ( 1059 // DatabaseProtocolPostgreSQL is the PostgreSQL database protocol. 1060 DatabaseProtocolPostgreSQL = "postgres" 1061 // DatabaseProtocolClickHouseHTTP is the ClickHouse database HTTP protocol. 1062 DatabaseProtocolClickHouseHTTP = "clickhouse-http" 1063 // DatabaseProtocolClickHouse is the ClickHouse database native write protocol. 1064 DatabaseProtocolClickHouse = "clickhouse" 1065 // DatabaseProtocolMySQL is the MySQL database protocol. 1066 DatabaseProtocolMySQL = "mysql" 1067 // DatabaseProtocolMongoDB is the MongoDB database protocol. 1068 DatabaseProtocolMongoDB = "mongodb" 1069 1070 // DatabaseTypeSelfHosted is the self-hosted type of database. 1071 DatabaseTypeSelfHosted = "self-hosted" 1072 // DatabaseTypeRDS is AWS-hosted RDS or Aurora database. 1073 DatabaseTypeRDS = "rds" 1074 // DatabaseTypeRDSProxy is an AWS-hosted RDS Proxy. 1075 DatabaseTypeRDSProxy = "rdsproxy" 1076 // DatabaseTypeRedshift is AWS Redshift database. 1077 DatabaseTypeRedshift = "redshift" 1078 // DatabaseTypeRedshiftServerless is AWS Redshift Serverless database. 1079 DatabaseTypeRedshiftServerless = "redshift-serverless" 1080 // DatabaseTypeCloudSQL is GCP-hosted Cloud SQL database. 1081 DatabaseTypeCloudSQL = "gcp" 1082 // DatabaseTypeAzure is Azure-hosted database. 1083 DatabaseTypeAzure = "azure" 1084 // DatabaseTypeElastiCache is AWS-hosted ElastiCache database. 1085 DatabaseTypeElastiCache = "elasticache" 1086 // DatabaseTypeMemoryDB is AWS-hosted MemoryDB database. 1087 DatabaseTypeMemoryDB = "memorydb" 1088 // DatabaseTypeAWSKeyspaces is AWS-hosted Keyspaces database (Cassandra). 1089 DatabaseTypeAWSKeyspaces = "keyspace" 1090 // DatabaseTypeCassandra is AWS-hosted Keyspace database. 1091 DatabaseTypeCassandra = "cassandra" 1092 // DatabaseTypeDynamoDB is a DynamoDB database. 1093 DatabaseTypeDynamoDB = "dynamodb" 1094 // DatabaseTypeOpenSearch is AWS-hosted OpenSearch instance. 1095 DatabaseTypeOpenSearch = "opensearch" 1096 // DatabaseTypeMongoAtlas 1097 DatabaseTypeMongoAtlas = "mongo-atlas" 1098 ) 1099 1100 // GetServerName returns the GCP database project and instance as "<project-id>:<instance-id>". 1101 func (gcp GCPCloudSQL) GetServerName() string { 1102 return fmt.Sprintf("%s:%s", gcp.ProjectID, gcp.InstanceID) 1103 } 1104 1105 // DeduplicateDatabases deduplicates databases by name. 1106 func DeduplicateDatabases(databases []Database) (result []Database) { 1107 seen := make(map[string]struct{}) 1108 for _, database := range databases { 1109 if _, ok := seen[database.GetName()]; ok { 1110 continue 1111 } 1112 seen[database.GetName()] = struct{}{} 1113 result = append(result, database) 1114 } 1115 return result 1116 } 1117 1118 // Databases is a list of database resources. 1119 type Databases []Database 1120 1121 // ToMap returns these databases as a map keyed by database name. 1122 func (d Databases) ToMap() map[string]Database { 1123 m := make(map[string]Database) 1124 for _, database := range d { 1125 m[database.GetName()] = database 1126 } 1127 return m 1128 } 1129 1130 // AsResources returns these databases as resources with labels. 1131 func (d Databases) AsResources() (resources ResourcesWithLabels) { 1132 for _, database := range d { 1133 resources = append(resources, database) 1134 } 1135 return resources 1136 } 1137 1138 // Len returns the slice length. 1139 func (d Databases) Len() int { return len(d) } 1140 1141 // Less compares databases by name. 1142 func (d Databases) Less(i, j int) bool { return d[i].GetName() < d[j].GetName() } 1143 1144 // Swap swaps two databases. 1145 func (d Databases) Swap(i, j int) { d[i], d[j] = d[j], d[i] } 1146 1147 // UnmarshalJSON supports parsing DatabaseTLSMode from number or string. 1148 func (d *DatabaseTLSMode) UnmarshalJSON(data []byte) error { 1149 type loopBreaker DatabaseTLSMode 1150 var val loopBreaker 1151 // try as number first. 1152 if err := json.Unmarshal(data, &val); err == nil { 1153 *d = DatabaseTLSMode(val) 1154 return nil 1155 } 1156 1157 // fallback to string. 1158 var s string 1159 if err := json.Unmarshal(data, &s); err != nil { 1160 return trace.Wrap(err) 1161 } 1162 return d.decodeName(s) 1163 } 1164 1165 // UnmarshalYAML supports parsing DatabaseTLSMode from number or string. 1166 func (d *DatabaseTLSMode) UnmarshalYAML(unmarshal func(interface{}) error) error { 1167 // try as number first. 1168 type loopBreaker DatabaseTLSMode 1169 var val loopBreaker 1170 if err := unmarshal(&val); err == nil { 1171 *d = DatabaseTLSMode(val) 1172 return nil 1173 } 1174 1175 // fallback to string. 1176 var s string 1177 if err := unmarshal(&s); err != nil { 1178 return trace.Wrap(err) 1179 } 1180 return d.decodeName(s) 1181 } 1182 1183 // decodeName decodes DatabaseTLSMode from a string. This is necessary for 1184 // allowing tctl commands to work with the same names as documented in Teleport 1185 // configuration, rather than requiring it be specified as an unreadable enum 1186 // number. 1187 func (d *DatabaseTLSMode) decodeName(name string) error { 1188 switch name { 1189 case "verify-full", "": 1190 *d = DatabaseTLSMode_VERIFY_FULL 1191 return nil 1192 case "verify-ca": 1193 *d = DatabaseTLSMode_VERIFY_CA 1194 return nil 1195 case "insecure": 1196 *d = DatabaseTLSMode_INSECURE 1197 return nil 1198 } 1199 return trace.BadParameter("DatabaseTLSMode invalid value %v", d) 1200 } 1201 1202 // MarshalJSON supports marshaling enum value into it's string value. 1203 func (s *IAMPolicyStatus) MarshalJSON() ([]byte, error) { 1204 return json.Marshal(s.String()) 1205 } 1206 1207 // UnmarshalJSON supports unmarshaling enum string value back to number. 1208 func (s *IAMPolicyStatus) UnmarshalJSON(data []byte) error { 1209 if len(data) == 0 { 1210 return nil 1211 } 1212 1213 var stringVal string 1214 if err := json.Unmarshal(data, &stringVal); err != nil { 1215 return err 1216 } 1217 1218 *s = IAMPolicyStatus(IAMPolicyStatus_value[stringVal]) 1219 return nil 1220 } 1221 1222 // IsAuditLogEnabled returns if Oracle Audit Log was enabled 1223 func (o OracleOptions) IsAuditLogEnabled() bool { 1224 return o.AuditUser != "" 1225 }