diff --git a/examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_databases.yaml b/examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_databases.yaml
new file mode 100644
index 0000000000000..78bd2907a8362
--- /dev/null
+++ b/examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_databases.yaml
@@ -0,0 +1,456 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ creationTimestamp: null
+ name: teleportdatabases.resources.teleport.dev
+spec:
+ group: resources.teleport.dev
+ names:
+ kind: TeleportDatabase
+ listKind: TeleportDatabaseList
+ plural: teleportdatabases
+ shortNames:
+ - database
+ - databases
+ singular: teleportdatabase
+ scope: Namespaced
+ versions:
+ - name: v3
+ schema:
+ openAPIV3Schema:
+ description: Database is the Schema for the databases API
+ properties:
+ apiVersion:
+ description: 'APIVersion defines the versioned schema of this representation
+ of an object. Servers should convert recognized schemas to the latest
+ internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+ type: string
+ kind:
+ description: 'Kind is a string value representing the REST resource this
+ object represents. Servers may infer this from the endpoint the client
+ submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ metadata:
+ type: object
+ spec:
+ description: Database resource definition v3 from Teleport
+ properties:
+ ad:
+ description: AD is the Active Directory configuration for the database.
+ properties:
+ domain:
+ description: Domain is the Active Directory domain the database
+ resides in.
+ type: string
+ kdc_host_name:
+ description: KDCHostName is the host name for a KDC for x509 Authentication.
+ type: string
+ keytab_file:
+ description: KeytabFile is the path to the Kerberos keytab file.
+ type: string
+ krb5_file:
+ description: Krb5File is the path to the Kerberos configuration
+ file. Defaults to /etc/krb5.conf.
+ type: string
+ ldap_cert:
+ description: LDAPCert is a certificate from Windows LDAP/AD, optional;
+ only for x509 Authentication.
+ type: string
+ spn:
+ description: SPN is the service principal name for the database.
+ type: string
+ type: object
+ admin_user:
+ description: AdminUser is the database admin user for automatic user
+ provisioning.
+ nullable: true
+ properties:
+ default_database:
+ description: DefaultDatabase is the database that the privileged
+ database user logs into by default. Depending on the database
+ type, this database may be used to store procedures or data
+ for managing database users.
+ type: string
+ name:
+ description: Name is the username of the privileged database user.
+ type: string
+ type: object
+ aws:
+ description: AWS contains AWS specific settings for RDS/Aurora/Redshift
+ databases.
+ properties:
+ account_id:
+ description: AccountID is the AWS account ID this database belongs
+ to.
+ type: string
+ assume_role_arn:
+ description: AssumeRoleARN is an optional AWS role ARN to assume
+ when accessing a database. Set this field and ExternalID to
+ enable access across AWS accounts.
+ type: string
+ docdb:
+ description: DocumentDB contains AWS DocumentDB specific metadata.
+ properties:
+ cluster_id:
+ description: ClusterID is the cluster identifier.
+ type: string
+ endpoint_type:
+ description: EndpointType is the type of the endpoint.
+ type: string
+ instance_id:
+ description: InstanceID is the instance identifier.
+ type: string
+ type: object
+ elasticache:
+ description: ElastiCache contains AWS ElastiCache Redis specific
+ metadata.
+ properties:
+ endpoint_type:
+ description: EndpointType is the type of the endpoint.
+ type: string
+ replication_group_id:
+ description: ReplicationGroupID is the Redis replication group
+ ID.
+ type: string
+ transit_encryption_enabled:
+ description: TransitEncryptionEnabled indicates whether in-transit
+ encryption (TLS) is enabled.
+ type: boolean
+ user_group_ids:
+ description: UserGroupIDs is a list of user group IDs.
+ items:
+ type: string
+ nullable: true
+ type: array
+ type: object
+ external_id:
+ description: ExternalID is an optional AWS external ID used to
+ enable assuming an AWS role across accounts.
+ type: string
+ iam_policy_status:
+ description: 'IAMPolicyStatus indicates whether the IAM Policy
+ is configured properly for database access. If not, the user
+ must update the AWS profile identity to allow access to the
+ Database. Eg for an RDS Database: the underlying AWS profile
+ allows for `rds-db:connect` for the Database.'
+ x-kubernetes-int-or-string: true
+ memorydb:
+ description: MemoryDB contains AWS MemoryDB specific metadata.
+ properties:
+ acl_name:
+ description: ACLName is the name of the ACL associated with
+ the cluster.
+ type: string
+ cluster_name:
+ description: ClusterName is the name of the MemoryDB cluster.
+ type: string
+ endpoint_type:
+ description: EndpointType is the type of the endpoint.
+ type: string
+ tls_enabled:
+ description: TLSEnabled indicates whether in-transit encryption
+ (TLS) is enabled.
+ type: boolean
+ type: object
+ opensearch:
+ description: OpenSearch contains AWS OpenSearch specific metadata.
+ properties:
+ domain_id:
+ description: DomainID is the ID of the domain.
+ type: string
+ domain_name:
+ description: DomainName is the name of the domain.
+ type: string
+ endpoint_type:
+ description: EndpointType is the type of the endpoint.
+ type: string
+ type: object
+ rds:
+ description: RDS contains RDS specific metadata.
+ properties:
+ cluster_id:
+ description: ClusterID is the RDS cluster (Aurora) identifier.
+ type: string
+ iam_auth:
+ description: IAMAuth indicates whether database IAM authentication
+ is enabled.
+ type: boolean
+ instance_id:
+ description: InstanceID is the RDS instance identifier.
+ type: string
+ resource_id:
+ description: ResourceID is the RDS instance resource identifier
+ (db-xxx).
+ type: string
+ security_groups:
+ description: SecurityGroups is a list of attached security
+ groups for the RDS instance.
+ items:
+ type: string
+ nullable: true
+ type: array
+ subnets:
+ description: Subnets is a list of subnets for the RDS instance.
+ items:
+ type: string
+ nullable: true
+ type: array
+ vpc_id:
+ description: VPCID is the VPC where the RDS is running.
+ type: string
+ type: object
+ rdsproxy:
+ description: RDSProxy contains AWS Proxy specific metadata.
+ properties:
+ custom_endpoint_name:
+ description: CustomEndpointName is the identifier of an RDS
+ Proxy custom endpoint.
+ type: string
+ name:
+ description: Name is the identifier of an RDS Proxy.
+ type: string
+ resource_id:
+ description: ResourceID is the RDS instance resource identifier
+ (prx-xxx).
+ type: string
+ type: object
+ redshift:
+ description: Redshift contains Redshift specific metadata.
+ properties:
+ cluster_id:
+ description: ClusterID is the Redshift cluster identifier.
+ type: string
+ type: object
+ redshift_serverless:
+ description: RedshiftServerless contains AWS Redshift Serverless
+ specific metadata.
+ properties:
+ endpoint_name:
+ description: EndpointName is the VPC endpoint name.
+ type: string
+ workgroup_id:
+ description: WorkgroupID is the workgroup ID.
+ type: string
+ workgroup_name:
+ description: WorkgroupName is the workgroup name.
+ type: string
+ type: object
+ region:
+ description: Region is a AWS cloud region.
+ type: string
+ secret_store:
+ description: SecretStore contains secret store configurations.
+ properties:
+ key_prefix:
+ description: KeyPrefix specifies the secret key prefix.
+ type: string
+ kms_key_id:
+ description: KMSKeyID specifies the AWS KMS key for encryption.
+ type: string
+ type: object
+ session_tags:
+ description: SessionTags is a list of AWS STS session tags.
+ nullable: true
+ properties:
+ key:
+ type: string
+ value:
+ type: string
+ type: object
+ type: object
+ azure:
+ description: Azure contains Azure specific database metadata.
+ properties:
+ is_flexi_server:
+ description: IsFlexiServer is true if the database is an Azure
+ Flexible server.
+ type: boolean
+ name:
+ description: Name is the Azure database server name.
+ type: string
+ redis:
+ description: Redis contains Azure Cache for Redis specific database
+ metadata.
+ properties:
+ clustering_policy:
+ description: ClusteringPolicy is the clustering policy for
+ Redis Enterprise.
+ type: string
+ type: object
+ resource_id:
+ description: ResourceID is the Azure fully qualified ID for the
+ resource.
+ type: string
+ type: object
+ ca_cert:
+ description: 'CACert is the PEM-encoded database CA certificate. DEPRECATED:
+ Moved to TLS.CACert. DELETE IN 10.0.'
+ type: string
+ dynamic_labels:
+ description: DynamicLabels is the database dynamic labels.
+ properties:
+ key:
+ type: string
+ value:
+ nullable: true
+ properties:
+ command:
+ description: Command is a command to run
+ items:
+ type: string
+ nullable: true
+ type: array
+ period:
+ description: Period is a time between command runs
+ format: duration
+ type: string
+ result:
+ description: Result captures standard output
+ type: string
+ type: object
+ type: object
+ gcp:
+ description: GCP contains parameters specific to GCP Cloud SQL databases.
+ properties:
+ instance_id:
+ description: InstanceID is the Cloud SQL instance ID.
+ type: string
+ project_id:
+ description: ProjectID is the GCP project ID the Cloud SQL instance
+ resides in.
+ type: string
+ type: object
+ mongo_atlas:
+ description: MongoAtlas contains Atlas metadata about the database.
+ properties:
+ name:
+ description: Name is the Atlas database instance name.
+ type: string
+ type: object
+ mysql:
+ description: MySQL is an additional section with MySQL database options.
+ properties:
+ server_version:
+ description: ServerVersion is the server version reported by DB
+ proxy if the runtime information is not available.
+ type: string
+ type: object
+ oracle:
+ description: Oracle is an additional Oracle configuration options.
+ properties:
+ audit_user:
+ description: AuditUser is the Oracle database user privilege to
+ access internal Oracle audit trail.
+ type: string
+ type: object
+ protocol:
+ description: 'Protocol is the database protocol: postgres, mysql,
+ mongodb, etc.'
+ type: string
+ tls:
+ description: TLS is the TLS configuration used when establishing connection
+ to target database. Allows to provide custom CA cert or override
+ server name.
+ properties:
+ ca_cert:
+ description: CACert is an optional user provided CA certificate
+ used for verifying database TLS connection.
+ type: string
+ mode:
+ description: Mode is a TLS connection mode. 0 is "verify-full";
+ 1 is "verify-ca", 2 is "insecure".
+ x-kubernetes-int-or-string: true
+ server_name:
+ description: ServerName allows to provide custom hostname. This
+ value will override the servername/hostname on a certificate
+ during validation.
+ type: string
+ trust_system_cert_pool:
+ description: TrustSystemCertPool allows Teleport to trust certificate
+ authorities available on the host system. If not set (by default),
+ Teleport only trusts self-signed databases with TLS certificates
+ signed by Teleport's Database Server CA or the ca_cert specified
+ in this TLS setting. For cloud-hosted databases, Teleport downloads
+ the corresponding required CAs for validation.
+ type: boolean
+ type: object
+ uri:
+ description: URI is the database connection endpoint.
+ type: string
+ type: object
+ status:
+ description: Status defines the observed state of the Teleport resource
+ properties:
+ conditions:
+ description: Conditions represent the latest available observations
+ of an object's state
+ items:
+ description: Condition contains details for one aspect of the current
+ state of this API Resource.
+ properties:
+ lastTransitionTime:
+ description: |-
+ lastTransitionTime is the last time the condition transitioned from one status to another.
+ This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
+ format: date-time
+ type: string
+ message:
+ description: |-
+ message is a human readable message indicating details about the transition.
+ This may be an empty string.
+ maxLength: 32768
+ type: string
+ observedGeneration:
+ description: |-
+ observedGeneration represents the .metadata.generation that the condition was set based upon.
+ For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
+ with respect to the current state of the instance.
+ format: int64
+ minimum: 0
+ type: integer
+ reason:
+ description: |-
+ reason contains a programmatic identifier indicating the reason for the condition's last transition.
+ Producers of specific condition types may define expected values and meanings for this field,
+ and whether the values are considered a guaranteed API.
+ The value should be a CamelCase string.
+ This field may not be empty.
+ maxLength: 1024
+ minLength: 1
+ pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+ type: string
+ status:
+ description: status of the condition, one of True, False, Unknown.
+ enum:
+ - "True"
+ - "False"
+ - Unknown
+ type: string
+ type:
+ description: type of condition in CamelCase or in foo.example.com/CamelCase.
+ maxLength: 316
+ pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+ type: string
+ required:
+ - lastTransitionTime
+ - message
+ - reason
+ - status
+ - type
+ type: object
+ type: array
+ teleportResourceID:
+ format: int64
+ type: integer
+ type: object
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
+status:
+ acceptedNames:
+ kind: ""
+ plural: ""
+ conditions: null
+ storedVersions: null
diff --git a/examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_databasesv3.yaml b/examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_databasesv3.yaml
new file mode 100644
index 0000000000000..96d97f36570b9
--- /dev/null
+++ b/examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_databasesv3.yaml
@@ -0,0 +1,456 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ creationTimestamp: null
+ name: teleportdatabasesv3.resources.teleport.dev
+spec:
+ group: resources.teleport.dev
+ names:
+ kind: TeleportDatabaseV3
+ listKind: TeleportDatabaseV3List
+ plural: teleportdatabasesv3
+ shortNames:
+ - databasev3
+ - databasesv3
+ singular: teleportdatabasev3
+ scope: Namespaced
+ versions:
+ - name: v1
+ schema:
+ openAPIV3Schema:
+ description: DatabaseV3 is the Schema for the databasesv3 API
+ properties:
+ apiVersion:
+ description: 'APIVersion defines the versioned schema of this representation
+ of an object. Servers should convert recognized schemas to the latest
+ internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+ type: string
+ kind:
+ description: 'Kind is a string value representing the REST resource this
+ object represents. Servers may infer this from the endpoint the client
+ submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ metadata:
+ type: object
+ spec:
+ description: Database resource definition v3 from Teleport
+ properties:
+ ad:
+ description: AD is the Active Directory configuration for the database.
+ properties:
+ domain:
+ description: Domain is the Active Directory domain the database
+ resides in.
+ type: string
+ kdc_host_name:
+ description: KDCHostName is the host name for a KDC for x509 Authentication.
+ type: string
+ keytab_file:
+ description: KeytabFile is the path to the Kerberos keytab file.
+ type: string
+ krb5_file:
+ description: Krb5File is the path to the Kerberos configuration
+ file. Defaults to /etc/krb5.conf.
+ type: string
+ ldap_cert:
+ description: LDAPCert is a certificate from Windows LDAP/AD, optional;
+ only for x509 Authentication.
+ type: string
+ spn:
+ description: SPN is the service principal name for the database.
+ type: string
+ type: object
+ admin_user:
+ description: AdminUser is the database admin user for automatic user
+ provisioning.
+ nullable: true
+ properties:
+ default_database:
+ description: DefaultDatabase is the database that the privileged
+ database user logs into by default. Depending on the database
+ type, this database may be used to store procedures or data
+ for managing database users.
+ type: string
+ name:
+ description: Name is the username of the privileged database user.
+ type: string
+ type: object
+ aws:
+ description: AWS contains AWS specific settings for RDS/Aurora/Redshift
+ databases.
+ properties:
+ account_id:
+ description: AccountID is the AWS account ID this database belongs
+ to.
+ type: string
+ assume_role_arn:
+ description: AssumeRoleARN is an optional AWS role ARN to assume
+ when accessing a database. Set this field and ExternalID to
+ enable access across AWS accounts.
+ type: string
+ docdb:
+ description: DocumentDB contains AWS DocumentDB specific metadata.
+ properties:
+ cluster_id:
+ description: ClusterID is the cluster identifier.
+ type: string
+ endpoint_type:
+ description: EndpointType is the type of the endpoint.
+ type: string
+ instance_id:
+ description: InstanceID is the instance identifier.
+ type: string
+ type: object
+ elasticache:
+ description: ElastiCache contains AWS ElastiCache Redis specific
+ metadata.
+ properties:
+ endpoint_type:
+ description: EndpointType is the type of the endpoint.
+ type: string
+ replication_group_id:
+ description: ReplicationGroupID is the Redis replication group
+ ID.
+ type: string
+ transit_encryption_enabled:
+ description: TransitEncryptionEnabled indicates whether in-transit
+ encryption (TLS) is enabled.
+ type: boolean
+ user_group_ids:
+ description: UserGroupIDs is a list of user group IDs.
+ items:
+ type: string
+ nullable: true
+ type: array
+ type: object
+ external_id:
+ description: ExternalID is an optional AWS external ID used to
+ enable assuming an AWS role across accounts.
+ type: string
+ iam_policy_status:
+ description: 'IAMPolicyStatus indicates whether the IAM Policy
+ is configured properly for database access. If not, the user
+ must update the AWS profile identity to allow access to the
+ Database. Eg for an RDS Database: the underlying AWS profile
+ allows for `rds-db:connect` for the Database.'
+ x-kubernetes-int-or-string: true
+ memorydb:
+ description: MemoryDB contains AWS MemoryDB specific metadata.
+ properties:
+ acl_name:
+ description: ACLName is the name of the ACL associated with
+ the cluster.
+ type: string
+ cluster_name:
+ description: ClusterName is the name of the MemoryDB cluster.
+ type: string
+ endpoint_type:
+ description: EndpointType is the type of the endpoint.
+ type: string
+ tls_enabled:
+ description: TLSEnabled indicates whether in-transit encryption
+ (TLS) is enabled.
+ type: boolean
+ type: object
+ opensearch:
+ description: OpenSearch contains AWS OpenSearch specific metadata.
+ properties:
+ domain_id:
+ description: DomainID is the ID of the domain.
+ type: string
+ domain_name:
+ description: DomainName is the name of the domain.
+ type: string
+ endpoint_type:
+ description: EndpointType is the type of the endpoint.
+ type: string
+ type: object
+ rds:
+ description: RDS contains RDS specific metadata.
+ properties:
+ cluster_id:
+ description: ClusterID is the RDS cluster (Aurora) identifier.
+ type: string
+ iam_auth:
+ description: IAMAuth indicates whether database IAM authentication
+ is enabled.
+ type: boolean
+ instance_id:
+ description: InstanceID is the RDS instance identifier.
+ type: string
+ resource_id:
+ description: ResourceID is the RDS instance resource identifier
+ (db-xxx).
+ type: string
+ security_groups:
+ description: SecurityGroups is a list of attached security
+ groups for the RDS instance.
+ items:
+ type: string
+ nullable: true
+ type: array
+ subnets:
+ description: Subnets is a list of subnets for the RDS instance.
+ items:
+ type: string
+ nullable: true
+ type: array
+ vpc_id:
+ description: VPCID is the VPC where the RDS is running.
+ type: string
+ type: object
+ rdsproxy:
+ description: RDSProxy contains AWS Proxy specific metadata.
+ properties:
+ custom_endpoint_name:
+ description: CustomEndpointName is the identifier of an RDS
+ Proxy custom endpoint.
+ type: string
+ name:
+ description: Name is the identifier of an RDS Proxy.
+ type: string
+ resource_id:
+ description: ResourceID is the RDS instance resource identifier
+ (prx-xxx).
+ type: string
+ type: object
+ redshift:
+ description: Redshift contains Redshift specific metadata.
+ properties:
+ cluster_id:
+ description: ClusterID is the Redshift cluster identifier.
+ type: string
+ type: object
+ redshift_serverless:
+ description: RedshiftServerless contains AWS Redshift Serverless
+ specific metadata.
+ properties:
+ endpoint_name:
+ description: EndpointName is the VPC endpoint name.
+ type: string
+ workgroup_id:
+ description: WorkgroupID is the workgroup ID.
+ type: string
+ workgroup_name:
+ description: WorkgroupName is the workgroup name.
+ type: string
+ type: object
+ region:
+ description: Region is a AWS cloud region.
+ type: string
+ secret_store:
+ description: SecretStore contains secret store configurations.
+ properties:
+ key_prefix:
+ description: KeyPrefix specifies the secret key prefix.
+ type: string
+ kms_key_id:
+ description: KMSKeyID specifies the AWS KMS key for encryption.
+ type: string
+ type: object
+ session_tags:
+ description: SessionTags is a list of AWS STS session tags.
+ nullable: true
+ properties:
+ key:
+ type: string
+ value:
+ type: string
+ type: object
+ type: object
+ azure:
+ description: Azure contains Azure specific database metadata.
+ properties:
+ is_flexi_server:
+ description: IsFlexiServer is true if the database is an Azure
+ Flexible server.
+ type: boolean
+ name:
+ description: Name is the Azure database server name.
+ type: string
+ redis:
+ description: Redis contains Azure Cache for Redis specific database
+ metadata.
+ properties:
+ clustering_policy:
+ description: ClusteringPolicy is the clustering policy for
+ Redis Enterprise.
+ type: string
+ type: object
+ resource_id:
+ description: ResourceID is the Azure fully qualified ID for the
+ resource.
+ type: string
+ type: object
+ ca_cert:
+ description: 'CACert is the PEM-encoded database CA certificate. DEPRECATED:
+ Moved to TLS.CACert. DELETE IN 10.0.'
+ type: string
+ dynamic_labels:
+ description: DynamicLabels is the database dynamic labels.
+ properties:
+ key:
+ type: string
+ value:
+ nullable: true
+ properties:
+ command:
+ description: Command is a command to run
+ items:
+ type: string
+ nullable: true
+ type: array
+ period:
+ description: Period is a time between command runs
+ format: duration
+ type: string
+ result:
+ description: Result captures standard output
+ type: string
+ type: object
+ type: object
+ gcp:
+ description: GCP contains parameters specific to GCP Cloud SQL databases.
+ properties:
+ instance_id:
+ description: InstanceID is the Cloud SQL instance ID.
+ type: string
+ project_id:
+ description: ProjectID is the GCP project ID the Cloud SQL instance
+ resides in.
+ type: string
+ type: object
+ mongo_atlas:
+ description: MongoAtlas contains Atlas metadata about the database.
+ properties:
+ name:
+ description: Name is the Atlas database instance name.
+ type: string
+ type: object
+ mysql:
+ description: MySQL is an additional section with MySQL database options.
+ properties:
+ server_version:
+ description: ServerVersion is the server version reported by DB
+ proxy if the runtime information is not available.
+ type: string
+ type: object
+ oracle:
+ description: Oracle is an additional Oracle configuration options.
+ properties:
+ audit_user:
+ description: AuditUser is the Oracle database user privilege to
+ access internal Oracle audit trail.
+ type: string
+ type: object
+ protocol:
+ description: 'Protocol is the database protocol: postgres, mysql,
+ mongodb, etc.'
+ type: string
+ tls:
+ description: TLS is the TLS configuration used when establishing connection
+ to target database. Allows to provide custom CA cert or override
+ server name.
+ properties:
+ ca_cert:
+ description: CACert is an optional user provided CA certificate
+ used for verifying database TLS connection.
+ type: string
+ mode:
+ description: Mode is a TLS connection mode. 0 is "verify-full";
+ 1 is "verify-ca", 2 is "insecure".
+ x-kubernetes-int-or-string: true
+ server_name:
+ description: ServerName allows to provide custom hostname. This
+ value will override the servername/hostname on a certificate
+ during validation.
+ type: string
+ trust_system_cert_pool:
+ description: TrustSystemCertPool allows Teleport to trust certificate
+ authorities available on the host system. If not set (by default),
+ Teleport only trusts self-signed databases with TLS certificates
+ signed by Teleport's Database Server CA or the ca_cert specified
+ in this TLS setting. For cloud-hosted databases, Teleport downloads
+ the corresponding required CAs for validation.
+ type: boolean
+ type: object
+ uri:
+ description: URI is the database connection endpoint.
+ type: string
+ type: object
+ status:
+ description: Status defines the observed state of the Teleport resource
+ properties:
+ conditions:
+ description: Conditions represent the latest available observations
+ of an object's state
+ items:
+ description: Condition contains details for one aspect of the current
+ state of this API Resource.
+ properties:
+ lastTransitionTime:
+ description: |-
+ lastTransitionTime is the last time the condition transitioned from one status to another.
+ This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
+ format: date-time
+ type: string
+ message:
+ description: |-
+ message is a human readable message indicating details about the transition.
+ This may be an empty string.
+ maxLength: 32768
+ type: string
+ observedGeneration:
+ description: |-
+ observedGeneration represents the .metadata.generation that the condition was set based upon.
+ For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
+ with respect to the current state of the instance.
+ format: int64
+ minimum: 0
+ type: integer
+ reason:
+ description: |-
+ reason contains a programmatic identifier indicating the reason for the condition's last transition.
+ Producers of specific condition types may define expected values and meanings for this field,
+ and whether the values are considered a guaranteed API.
+ The value should be a CamelCase string.
+ This field may not be empty.
+ maxLength: 1024
+ minLength: 1
+ pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+ type: string
+ status:
+ description: status of the condition, one of True, False, Unknown.
+ enum:
+ - "True"
+ - "False"
+ - Unknown
+ type: string
+ type:
+ description: type of condition in CamelCase or in foo.example.com/CamelCase.
+ maxLength: 316
+ pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+ type: string
+ required:
+ - lastTransitionTime
+ - message
+ - reason
+ - status
+ - type
+ type: object
+ type: array
+ teleportResourceID:
+ format: int64
+ type: integer
+ type: object
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
+status:
+ acceptedNames:
+ kind: ""
+ plural: ""
+ conditions: null
+ storedVersions: null
diff --git a/examples/chart/teleport-cluster/templates/auth/config.yaml b/examples/chart/teleport-cluster/templates/auth/config.yaml
index 99fe59e061c9c..d89ad2d2d832e 100644
--- a/examples/chart/teleport-cluster/templates/auth/config.yaml
+++ b/examples/chart/teleport-cluster/templates/auth/config.yaml
@@ -131,6 +131,19 @@ data:
- read
- update
- delete
+ - resources:
+ - db
+ verbs:
+ - list
+ - create
+ - read
+ - update
+ - delete
+ impersonate:
+ users:
+ - Db
+ roles:
+ - Db
deny: {}
version: v7
---
diff --git a/integration/helpers/helpers.go b/integration/helpers/helpers.go
index fdede24b0209a..69a22e9139c17 100644
--- a/integration/helpers/helpers.go
+++ b/integration/helpers/helpers.go
@@ -455,6 +455,7 @@ func MakeTestDatabaseServer(t *testing.T, proxyAddr utils.NetAddr, token string,
cfg.Databases.Databases = dbs
cfg.Databases.ResourceMatchers = resMatchers
cfg.Log = utils.NewLoggerForTests()
+ cfg.DebugService.Enabled = false
db, err := service.NewTeleport(cfg)
require.NoError(t, err)
@@ -465,8 +466,8 @@ func MakeTestDatabaseServer(t *testing.T, proxyAddr utils.NetAddr, token string,
})
// Wait for database agent to start.
- _, err = db.WaitForEventTimeout(30*time.Second, service.DatabasesReady)
- require.NoError(t, err, "database server didn't start after 10s")
+ err = db.Start()
+ require.NoError(t, err, "database server didn't start after 30s")
return db
}
diff --git a/integrations/operator/Makefile b/integrations/operator/Makefile
index 4073de70029df..1cfc2ee12c2d6 100644
--- a/integrations/operator/Makefile
+++ b/integrations/operator/Makefile
@@ -137,10 +137,10 @@ vet: ## Run go vet against code.
go vet ./...
.PHONY: test
-test: manifests generate fmt vet $(ENVTEST) crdgen-test ## Run tests.
+test: $(ENVTEST) ## Run tests.
test: export KUBEBUILDER_ASSETS=$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)
test:
- go test ./... -coverprofile cover.out
+ go test -run ^TestTeleportDatabaseV3Creation$$ github.com/gravitational/teleport/integrations/operator/controllers/resources
.PHONY: echo-kubebuilder-assets
echo-kubebuilder-assets:
diff --git a/integrations/operator/apis/resources/v1/databasev3_types.go b/integrations/operator/apis/resources/v1/databasev3_types.go
new file mode 100644
index 0000000000000..54a87ca2bb753
--- /dev/null
+++ b/integrations/operator/apis/resources/v1/databasev3_types.go
@@ -0,0 +1,95 @@
+/*
+ * Teleport
+ * Copyright (C) 2023 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package v1
+
+import (
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ "github.com/gravitational/teleport/api/types"
+ "github.com/gravitational/teleport/integrations/operator/apis/resources"
+)
+
+func init() {
+ SchemeBuilder.Register(&TeleportDatabaseV3{}, &TeleportDatabaseListV3{})
+}
+
+// TeleportDatabaseSpec defines the desired state of TeleportDatabase
+type TeleportDatabaseSpec types.DatabaseSpecV3
+
+//+kubebuilder:object:root=true
+//+kubebuilder:subresource:status
+
+// TeleportDatabase is the Schema for the databases API
+type TeleportDatabaseV3 struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ObjectMeta `json:"metadata,omitempty"`
+
+ Spec TeleportDatabaseSpec `json:"spec,omitempty"`
+ Status resources.Status `json:"status,omitempty"`
+}
+
+//+kubebuilder:object:root=true
+
+// TeleportDatabaseList contains a list of TeleportDatabase
+type TeleportDatabaseListV3 struct {
+ metav1.TypeMeta `json:",inline"`
+ metav1.ListMeta `json:"metadata,omitempty"`
+ Items []TeleportDatabaseV3 `json:"items"`
+}
+
+func (r TeleportDatabaseV3) ToTeleport() types.Database {
+ return &types.DatabaseV3{
+ Kind: types.KindDatabase,
+ Version: types.V3,
+ Metadata: types.Metadata{
+ Name: r.Name,
+ Labels: r.Labels,
+ Description: r.Annotations[resources.DescriptionKey],
+ },
+ Spec: types.DatabaseSpecV3(r.Spec),
+ }
+}
+
+// Marshal serializes a spec into binary data.
+func (spec *TeleportDatabaseSpec) Marshal() ([]byte, error) {
+ return (*types.DatabaseSpecV3)(spec).Marshal()
+}
+
+// Unmarshal deserializes a spec from binary data.
+func (spec *TeleportDatabaseSpec) Unmarshal(data []byte) error {
+ return (*types.DatabaseSpecV3)(spec).Unmarshal(data)
+}
+
+// DeepCopyInto deep-copies one database spec into another.
+// Required to satisfy runtime.Object interface.
+func (spec *TeleportDatabaseSpec) DeepCopyInto(out *TeleportDatabaseSpec) {
+ data, err := spec.Marshal()
+ if err != nil {
+ panic(err)
+ }
+ *out = TeleportDatabaseSpec{}
+ if err = out.Unmarshal(data); err != nil {
+ panic(err)
+ }
+}
+
+// StatusConditions returns a pointer to Status.Conditions slice.
+func (r *TeleportDatabaseV3) StatusConditions() *[]metav1.Condition {
+ return &r.Status.Conditions
+}
diff --git a/integrations/operator/apis/resources/v1/zz_generated.deepcopy.go b/integrations/operator/apis/resources/v1/zz_generated.deepcopy.go
index e2f6b7ce932c1..b8d7d14c7c8e1 100644
--- a/integrations/operator/apis/resources/v1/zz_generated.deepcopy.go
+++ b/integrations/operator/apis/resources/v1/zz_generated.deepcopy.go
@@ -95,6 +95,75 @@ func (in *TeleportAccessListSpec) DeepCopy() *TeleportAccessListSpec {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TeleportDatabaseListV3) DeepCopyInto(out *TeleportDatabaseListV3) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ListMeta.DeepCopyInto(&out.ListMeta)
+ if in.Items != nil {
+ in, out := &in.Items, &out.Items
+ *out = make([]TeleportDatabaseV3, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeleportDatabaseListV3.
+func (in *TeleportDatabaseListV3) DeepCopy() *TeleportDatabaseListV3 {
+ if in == nil {
+ return nil
+ }
+ out := new(TeleportDatabaseListV3)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *TeleportDatabaseListV3) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeleportDatabaseSpec.
+func (in *TeleportDatabaseSpec) DeepCopy() *TeleportDatabaseSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(TeleportDatabaseSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *TeleportDatabaseV3) DeepCopyInto(out *TeleportDatabaseV3) {
+ *out = *in
+ out.TypeMeta = in.TypeMeta
+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
+ in.Spec.DeepCopyInto(&out.Spec)
+ in.Status.DeepCopyInto(&out.Status)
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeleportDatabaseV3.
+func (in *TeleportDatabaseV3) DeepCopy() *TeleportDatabaseV3 {
+ if in == nil {
+ return nil
+ }
+ out := new(TeleportDatabaseV3)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
+func (in *TeleportDatabaseV3) DeepCopyObject() runtime.Object {
+ if c := in.DeepCopy(); c != nil {
+ return c
+ }
+ return nil
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TeleportLoginRule) DeepCopyInto(out *TeleportLoginRule) {
*out = *in
diff --git a/integrations/operator/config/crd/bases/resources.teleport.dev_databases.yaml b/integrations/operator/config/crd/bases/resources.teleport.dev_databases.yaml
new file mode 100644
index 0000000000000..78bd2907a8362
--- /dev/null
+++ b/integrations/operator/config/crd/bases/resources.teleport.dev_databases.yaml
@@ -0,0 +1,456 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ creationTimestamp: null
+ name: teleportdatabases.resources.teleport.dev
+spec:
+ group: resources.teleport.dev
+ names:
+ kind: TeleportDatabase
+ listKind: TeleportDatabaseList
+ plural: teleportdatabases
+ shortNames:
+ - database
+ - databases
+ singular: teleportdatabase
+ scope: Namespaced
+ versions:
+ - name: v3
+ schema:
+ openAPIV3Schema:
+ description: Database is the Schema for the databases API
+ properties:
+ apiVersion:
+ description: 'APIVersion defines the versioned schema of this representation
+ of an object. Servers should convert recognized schemas to the latest
+ internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+ type: string
+ kind:
+ description: 'Kind is a string value representing the REST resource this
+ object represents. Servers may infer this from the endpoint the client
+ submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ metadata:
+ type: object
+ spec:
+ description: Database resource definition v3 from Teleport
+ properties:
+ ad:
+ description: AD is the Active Directory configuration for the database.
+ properties:
+ domain:
+ description: Domain is the Active Directory domain the database
+ resides in.
+ type: string
+ kdc_host_name:
+ description: KDCHostName is the host name for a KDC for x509 Authentication.
+ type: string
+ keytab_file:
+ description: KeytabFile is the path to the Kerberos keytab file.
+ type: string
+ krb5_file:
+ description: Krb5File is the path to the Kerberos configuration
+ file. Defaults to /etc/krb5.conf.
+ type: string
+ ldap_cert:
+ description: LDAPCert is a certificate from Windows LDAP/AD, optional;
+ only for x509 Authentication.
+ type: string
+ spn:
+ description: SPN is the service principal name for the database.
+ type: string
+ type: object
+ admin_user:
+ description: AdminUser is the database admin user for automatic user
+ provisioning.
+ nullable: true
+ properties:
+ default_database:
+ description: DefaultDatabase is the database that the privileged
+ database user logs into by default. Depending on the database
+ type, this database may be used to store procedures or data
+ for managing database users.
+ type: string
+ name:
+ description: Name is the username of the privileged database user.
+ type: string
+ type: object
+ aws:
+ description: AWS contains AWS specific settings for RDS/Aurora/Redshift
+ databases.
+ properties:
+ account_id:
+ description: AccountID is the AWS account ID this database belongs
+ to.
+ type: string
+ assume_role_arn:
+ description: AssumeRoleARN is an optional AWS role ARN to assume
+ when accessing a database. Set this field and ExternalID to
+ enable access across AWS accounts.
+ type: string
+ docdb:
+ description: DocumentDB contains AWS DocumentDB specific metadata.
+ properties:
+ cluster_id:
+ description: ClusterID is the cluster identifier.
+ type: string
+ endpoint_type:
+ description: EndpointType is the type of the endpoint.
+ type: string
+ instance_id:
+ description: InstanceID is the instance identifier.
+ type: string
+ type: object
+ elasticache:
+ description: ElastiCache contains AWS ElastiCache Redis specific
+ metadata.
+ properties:
+ endpoint_type:
+ description: EndpointType is the type of the endpoint.
+ type: string
+ replication_group_id:
+ description: ReplicationGroupID is the Redis replication group
+ ID.
+ type: string
+ transit_encryption_enabled:
+ description: TransitEncryptionEnabled indicates whether in-transit
+ encryption (TLS) is enabled.
+ type: boolean
+ user_group_ids:
+ description: UserGroupIDs is a list of user group IDs.
+ items:
+ type: string
+ nullable: true
+ type: array
+ type: object
+ external_id:
+ description: ExternalID is an optional AWS external ID used to
+ enable assuming an AWS role across accounts.
+ type: string
+ iam_policy_status:
+ description: 'IAMPolicyStatus indicates whether the IAM Policy
+ is configured properly for database access. If not, the user
+ must update the AWS profile identity to allow access to the
+ Database. Eg for an RDS Database: the underlying AWS profile
+ allows for `rds-db:connect` for the Database.'
+ x-kubernetes-int-or-string: true
+ memorydb:
+ description: MemoryDB contains AWS MemoryDB specific metadata.
+ properties:
+ acl_name:
+ description: ACLName is the name of the ACL associated with
+ the cluster.
+ type: string
+ cluster_name:
+ description: ClusterName is the name of the MemoryDB cluster.
+ type: string
+ endpoint_type:
+ description: EndpointType is the type of the endpoint.
+ type: string
+ tls_enabled:
+ description: TLSEnabled indicates whether in-transit encryption
+ (TLS) is enabled.
+ type: boolean
+ type: object
+ opensearch:
+ description: OpenSearch contains AWS OpenSearch specific metadata.
+ properties:
+ domain_id:
+ description: DomainID is the ID of the domain.
+ type: string
+ domain_name:
+ description: DomainName is the name of the domain.
+ type: string
+ endpoint_type:
+ description: EndpointType is the type of the endpoint.
+ type: string
+ type: object
+ rds:
+ description: RDS contains RDS specific metadata.
+ properties:
+ cluster_id:
+ description: ClusterID is the RDS cluster (Aurora) identifier.
+ type: string
+ iam_auth:
+ description: IAMAuth indicates whether database IAM authentication
+ is enabled.
+ type: boolean
+ instance_id:
+ description: InstanceID is the RDS instance identifier.
+ type: string
+ resource_id:
+ description: ResourceID is the RDS instance resource identifier
+ (db-xxx).
+ type: string
+ security_groups:
+ description: SecurityGroups is a list of attached security
+ groups for the RDS instance.
+ items:
+ type: string
+ nullable: true
+ type: array
+ subnets:
+ description: Subnets is a list of subnets for the RDS instance.
+ items:
+ type: string
+ nullable: true
+ type: array
+ vpc_id:
+ description: VPCID is the VPC where the RDS is running.
+ type: string
+ type: object
+ rdsproxy:
+ description: RDSProxy contains AWS Proxy specific metadata.
+ properties:
+ custom_endpoint_name:
+ description: CustomEndpointName is the identifier of an RDS
+ Proxy custom endpoint.
+ type: string
+ name:
+ description: Name is the identifier of an RDS Proxy.
+ type: string
+ resource_id:
+ description: ResourceID is the RDS instance resource identifier
+ (prx-xxx).
+ type: string
+ type: object
+ redshift:
+ description: Redshift contains Redshift specific metadata.
+ properties:
+ cluster_id:
+ description: ClusterID is the Redshift cluster identifier.
+ type: string
+ type: object
+ redshift_serverless:
+ description: RedshiftServerless contains AWS Redshift Serverless
+ specific metadata.
+ properties:
+ endpoint_name:
+ description: EndpointName is the VPC endpoint name.
+ type: string
+ workgroup_id:
+ description: WorkgroupID is the workgroup ID.
+ type: string
+ workgroup_name:
+ description: WorkgroupName is the workgroup name.
+ type: string
+ type: object
+ region:
+ description: Region is a AWS cloud region.
+ type: string
+ secret_store:
+ description: SecretStore contains secret store configurations.
+ properties:
+ key_prefix:
+ description: KeyPrefix specifies the secret key prefix.
+ type: string
+ kms_key_id:
+ description: KMSKeyID specifies the AWS KMS key for encryption.
+ type: string
+ type: object
+ session_tags:
+ description: SessionTags is a list of AWS STS session tags.
+ nullable: true
+ properties:
+ key:
+ type: string
+ value:
+ type: string
+ type: object
+ type: object
+ azure:
+ description: Azure contains Azure specific database metadata.
+ properties:
+ is_flexi_server:
+ description: IsFlexiServer is true if the database is an Azure
+ Flexible server.
+ type: boolean
+ name:
+ description: Name is the Azure database server name.
+ type: string
+ redis:
+ description: Redis contains Azure Cache for Redis specific database
+ metadata.
+ properties:
+ clustering_policy:
+ description: ClusteringPolicy is the clustering policy for
+ Redis Enterprise.
+ type: string
+ type: object
+ resource_id:
+ description: ResourceID is the Azure fully qualified ID for the
+ resource.
+ type: string
+ type: object
+ ca_cert:
+ description: 'CACert is the PEM-encoded database CA certificate. DEPRECATED:
+ Moved to TLS.CACert. DELETE IN 10.0.'
+ type: string
+ dynamic_labels:
+ description: DynamicLabels is the database dynamic labels.
+ properties:
+ key:
+ type: string
+ value:
+ nullable: true
+ properties:
+ command:
+ description: Command is a command to run
+ items:
+ type: string
+ nullable: true
+ type: array
+ period:
+ description: Period is a time between command runs
+ format: duration
+ type: string
+ result:
+ description: Result captures standard output
+ type: string
+ type: object
+ type: object
+ gcp:
+ description: GCP contains parameters specific to GCP Cloud SQL databases.
+ properties:
+ instance_id:
+ description: InstanceID is the Cloud SQL instance ID.
+ type: string
+ project_id:
+ description: ProjectID is the GCP project ID the Cloud SQL instance
+ resides in.
+ type: string
+ type: object
+ mongo_atlas:
+ description: MongoAtlas contains Atlas metadata about the database.
+ properties:
+ name:
+ description: Name is the Atlas database instance name.
+ type: string
+ type: object
+ mysql:
+ description: MySQL is an additional section with MySQL database options.
+ properties:
+ server_version:
+ description: ServerVersion is the server version reported by DB
+ proxy if the runtime information is not available.
+ type: string
+ type: object
+ oracle:
+ description: Oracle is an additional Oracle configuration options.
+ properties:
+ audit_user:
+ description: AuditUser is the Oracle database user privilege to
+ access internal Oracle audit trail.
+ type: string
+ type: object
+ protocol:
+ description: 'Protocol is the database protocol: postgres, mysql,
+ mongodb, etc.'
+ type: string
+ tls:
+ description: TLS is the TLS configuration used when establishing connection
+ to target database. Allows to provide custom CA cert or override
+ server name.
+ properties:
+ ca_cert:
+ description: CACert is an optional user provided CA certificate
+ used for verifying database TLS connection.
+ type: string
+ mode:
+ description: Mode is a TLS connection mode. 0 is "verify-full";
+ 1 is "verify-ca", 2 is "insecure".
+ x-kubernetes-int-or-string: true
+ server_name:
+ description: ServerName allows to provide custom hostname. This
+ value will override the servername/hostname on a certificate
+ during validation.
+ type: string
+ trust_system_cert_pool:
+ description: TrustSystemCertPool allows Teleport to trust certificate
+ authorities available on the host system. If not set (by default),
+ Teleport only trusts self-signed databases with TLS certificates
+ signed by Teleport's Database Server CA or the ca_cert specified
+ in this TLS setting. For cloud-hosted databases, Teleport downloads
+ the corresponding required CAs for validation.
+ type: boolean
+ type: object
+ uri:
+ description: URI is the database connection endpoint.
+ type: string
+ type: object
+ status:
+ description: Status defines the observed state of the Teleport resource
+ properties:
+ conditions:
+ description: Conditions represent the latest available observations
+ of an object's state
+ items:
+ description: Condition contains details for one aspect of the current
+ state of this API Resource.
+ properties:
+ lastTransitionTime:
+ description: |-
+ lastTransitionTime is the last time the condition transitioned from one status to another.
+ This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
+ format: date-time
+ type: string
+ message:
+ description: |-
+ message is a human readable message indicating details about the transition.
+ This may be an empty string.
+ maxLength: 32768
+ type: string
+ observedGeneration:
+ description: |-
+ observedGeneration represents the .metadata.generation that the condition was set based upon.
+ For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
+ with respect to the current state of the instance.
+ format: int64
+ minimum: 0
+ type: integer
+ reason:
+ description: |-
+ reason contains a programmatic identifier indicating the reason for the condition's last transition.
+ Producers of specific condition types may define expected values and meanings for this field,
+ and whether the values are considered a guaranteed API.
+ The value should be a CamelCase string.
+ This field may not be empty.
+ maxLength: 1024
+ minLength: 1
+ pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+ type: string
+ status:
+ description: status of the condition, one of True, False, Unknown.
+ enum:
+ - "True"
+ - "False"
+ - Unknown
+ type: string
+ type:
+ description: type of condition in CamelCase or in foo.example.com/CamelCase.
+ maxLength: 316
+ pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+ type: string
+ required:
+ - lastTransitionTime
+ - message
+ - reason
+ - status
+ - type
+ type: object
+ type: array
+ teleportResourceID:
+ format: int64
+ type: integer
+ type: object
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
+status:
+ acceptedNames:
+ kind: ""
+ plural: ""
+ conditions: null
+ storedVersions: null
diff --git a/integrations/operator/config/crd/bases/resources.teleport.dev_databasesv3.yaml b/integrations/operator/config/crd/bases/resources.teleport.dev_databasesv3.yaml
new file mode 100644
index 0000000000000..96d97f36570b9
--- /dev/null
+++ b/integrations/operator/config/crd/bases/resources.teleport.dev_databasesv3.yaml
@@ -0,0 +1,456 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ creationTimestamp: null
+ name: teleportdatabasesv3.resources.teleport.dev
+spec:
+ group: resources.teleport.dev
+ names:
+ kind: TeleportDatabaseV3
+ listKind: TeleportDatabaseV3List
+ plural: teleportdatabasesv3
+ shortNames:
+ - databasev3
+ - databasesv3
+ singular: teleportdatabasev3
+ scope: Namespaced
+ versions:
+ - name: v1
+ schema:
+ openAPIV3Schema:
+ description: DatabaseV3 is the Schema for the databasesv3 API
+ properties:
+ apiVersion:
+ description: 'APIVersion defines the versioned schema of this representation
+ of an object. Servers should convert recognized schemas to the latest
+ internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+ type: string
+ kind:
+ description: 'Kind is a string value representing the REST resource this
+ object represents. Servers may infer this from the endpoint the client
+ submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ metadata:
+ type: object
+ spec:
+ description: Database resource definition v3 from Teleport
+ properties:
+ ad:
+ description: AD is the Active Directory configuration for the database.
+ properties:
+ domain:
+ description: Domain is the Active Directory domain the database
+ resides in.
+ type: string
+ kdc_host_name:
+ description: KDCHostName is the host name for a KDC for x509 Authentication.
+ type: string
+ keytab_file:
+ description: KeytabFile is the path to the Kerberos keytab file.
+ type: string
+ krb5_file:
+ description: Krb5File is the path to the Kerberos configuration
+ file. Defaults to /etc/krb5.conf.
+ type: string
+ ldap_cert:
+ description: LDAPCert is a certificate from Windows LDAP/AD, optional;
+ only for x509 Authentication.
+ type: string
+ spn:
+ description: SPN is the service principal name for the database.
+ type: string
+ type: object
+ admin_user:
+ description: AdminUser is the database admin user for automatic user
+ provisioning.
+ nullable: true
+ properties:
+ default_database:
+ description: DefaultDatabase is the database that the privileged
+ database user logs into by default. Depending on the database
+ type, this database may be used to store procedures or data
+ for managing database users.
+ type: string
+ name:
+ description: Name is the username of the privileged database user.
+ type: string
+ type: object
+ aws:
+ description: AWS contains AWS specific settings for RDS/Aurora/Redshift
+ databases.
+ properties:
+ account_id:
+ description: AccountID is the AWS account ID this database belongs
+ to.
+ type: string
+ assume_role_arn:
+ description: AssumeRoleARN is an optional AWS role ARN to assume
+ when accessing a database. Set this field and ExternalID to
+ enable access across AWS accounts.
+ type: string
+ docdb:
+ description: DocumentDB contains AWS DocumentDB specific metadata.
+ properties:
+ cluster_id:
+ description: ClusterID is the cluster identifier.
+ type: string
+ endpoint_type:
+ description: EndpointType is the type of the endpoint.
+ type: string
+ instance_id:
+ description: InstanceID is the instance identifier.
+ type: string
+ type: object
+ elasticache:
+ description: ElastiCache contains AWS ElastiCache Redis specific
+ metadata.
+ properties:
+ endpoint_type:
+ description: EndpointType is the type of the endpoint.
+ type: string
+ replication_group_id:
+ description: ReplicationGroupID is the Redis replication group
+ ID.
+ type: string
+ transit_encryption_enabled:
+ description: TransitEncryptionEnabled indicates whether in-transit
+ encryption (TLS) is enabled.
+ type: boolean
+ user_group_ids:
+ description: UserGroupIDs is a list of user group IDs.
+ items:
+ type: string
+ nullable: true
+ type: array
+ type: object
+ external_id:
+ description: ExternalID is an optional AWS external ID used to
+ enable assuming an AWS role across accounts.
+ type: string
+ iam_policy_status:
+ description: 'IAMPolicyStatus indicates whether the IAM Policy
+ is configured properly for database access. If not, the user
+ must update the AWS profile identity to allow access to the
+ Database. Eg for an RDS Database: the underlying AWS profile
+ allows for `rds-db:connect` for the Database.'
+ x-kubernetes-int-or-string: true
+ memorydb:
+ description: MemoryDB contains AWS MemoryDB specific metadata.
+ properties:
+ acl_name:
+ description: ACLName is the name of the ACL associated with
+ the cluster.
+ type: string
+ cluster_name:
+ description: ClusterName is the name of the MemoryDB cluster.
+ type: string
+ endpoint_type:
+ description: EndpointType is the type of the endpoint.
+ type: string
+ tls_enabled:
+ description: TLSEnabled indicates whether in-transit encryption
+ (TLS) is enabled.
+ type: boolean
+ type: object
+ opensearch:
+ description: OpenSearch contains AWS OpenSearch specific metadata.
+ properties:
+ domain_id:
+ description: DomainID is the ID of the domain.
+ type: string
+ domain_name:
+ description: DomainName is the name of the domain.
+ type: string
+ endpoint_type:
+ description: EndpointType is the type of the endpoint.
+ type: string
+ type: object
+ rds:
+ description: RDS contains RDS specific metadata.
+ properties:
+ cluster_id:
+ description: ClusterID is the RDS cluster (Aurora) identifier.
+ type: string
+ iam_auth:
+ description: IAMAuth indicates whether database IAM authentication
+ is enabled.
+ type: boolean
+ instance_id:
+ description: InstanceID is the RDS instance identifier.
+ type: string
+ resource_id:
+ description: ResourceID is the RDS instance resource identifier
+ (db-xxx).
+ type: string
+ security_groups:
+ description: SecurityGroups is a list of attached security
+ groups for the RDS instance.
+ items:
+ type: string
+ nullable: true
+ type: array
+ subnets:
+ description: Subnets is a list of subnets for the RDS instance.
+ items:
+ type: string
+ nullable: true
+ type: array
+ vpc_id:
+ description: VPCID is the VPC where the RDS is running.
+ type: string
+ type: object
+ rdsproxy:
+ description: RDSProxy contains AWS Proxy specific metadata.
+ properties:
+ custom_endpoint_name:
+ description: CustomEndpointName is the identifier of an RDS
+ Proxy custom endpoint.
+ type: string
+ name:
+ description: Name is the identifier of an RDS Proxy.
+ type: string
+ resource_id:
+ description: ResourceID is the RDS instance resource identifier
+ (prx-xxx).
+ type: string
+ type: object
+ redshift:
+ description: Redshift contains Redshift specific metadata.
+ properties:
+ cluster_id:
+ description: ClusterID is the Redshift cluster identifier.
+ type: string
+ type: object
+ redshift_serverless:
+ description: RedshiftServerless contains AWS Redshift Serverless
+ specific metadata.
+ properties:
+ endpoint_name:
+ description: EndpointName is the VPC endpoint name.
+ type: string
+ workgroup_id:
+ description: WorkgroupID is the workgroup ID.
+ type: string
+ workgroup_name:
+ description: WorkgroupName is the workgroup name.
+ type: string
+ type: object
+ region:
+ description: Region is a AWS cloud region.
+ type: string
+ secret_store:
+ description: SecretStore contains secret store configurations.
+ properties:
+ key_prefix:
+ description: KeyPrefix specifies the secret key prefix.
+ type: string
+ kms_key_id:
+ description: KMSKeyID specifies the AWS KMS key for encryption.
+ type: string
+ type: object
+ session_tags:
+ description: SessionTags is a list of AWS STS session tags.
+ nullable: true
+ properties:
+ key:
+ type: string
+ value:
+ type: string
+ type: object
+ type: object
+ azure:
+ description: Azure contains Azure specific database metadata.
+ properties:
+ is_flexi_server:
+ description: IsFlexiServer is true if the database is an Azure
+ Flexible server.
+ type: boolean
+ name:
+ description: Name is the Azure database server name.
+ type: string
+ redis:
+ description: Redis contains Azure Cache for Redis specific database
+ metadata.
+ properties:
+ clustering_policy:
+ description: ClusteringPolicy is the clustering policy for
+ Redis Enterprise.
+ type: string
+ type: object
+ resource_id:
+ description: ResourceID is the Azure fully qualified ID for the
+ resource.
+ type: string
+ type: object
+ ca_cert:
+ description: 'CACert is the PEM-encoded database CA certificate. DEPRECATED:
+ Moved to TLS.CACert. DELETE IN 10.0.'
+ type: string
+ dynamic_labels:
+ description: DynamicLabels is the database dynamic labels.
+ properties:
+ key:
+ type: string
+ value:
+ nullable: true
+ properties:
+ command:
+ description: Command is a command to run
+ items:
+ type: string
+ nullable: true
+ type: array
+ period:
+ description: Period is a time between command runs
+ format: duration
+ type: string
+ result:
+ description: Result captures standard output
+ type: string
+ type: object
+ type: object
+ gcp:
+ description: GCP contains parameters specific to GCP Cloud SQL databases.
+ properties:
+ instance_id:
+ description: InstanceID is the Cloud SQL instance ID.
+ type: string
+ project_id:
+ description: ProjectID is the GCP project ID the Cloud SQL instance
+ resides in.
+ type: string
+ type: object
+ mongo_atlas:
+ description: MongoAtlas contains Atlas metadata about the database.
+ properties:
+ name:
+ description: Name is the Atlas database instance name.
+ type: string
+ type: object
+ mysql:
+ description: MySQL is an additional section with MySQL database options.
+ properties:
+ server_version:
+ description: ServerVersion is the server version reported by DB
+ proxy if the runtime information is not available.
+ type: string
+ type: object
+ oracle:
+ description: Oracle is an additional Oracle configuration options.
+ properties:
+ audit_user:
+ description: AuditUser is the Oracle database user privilege to
+ access internal Oracle audit trail.
+ type: string
+ type: object
+ protocol:
+ description: 'Protocol is the database protocol: postgres, mysql,
+ mongodb, etc.'
+ type: string
+ tls:
+ description: TLS is the TLS configuration used when establishing connection
+ to target database. Allows to provide custom CA cert or override
+ server name.
+ properties:
+ ca_cert:
+ description: CACert is an optional user provided CA certificate
+ used for verifying database TLS connection.
+ type: string
+ mode:
+ description: Mode is a TLS connection mode. 0 is "verify-full";
+ 1 is "verify-ca", 2 is "insecure".
+ x-kubernetes-int-or-string: true
+ server_name:
+ description: ServerName allows to provide custom hostname. This
+ value will override the servername/hostname on a certificate
+ during validation.
+ type: string
+ trust_system_cert_pool:
+ description: TrustSystemCertPool allows Teleport to trust certificate
+ authorities available on the host system. If not set (by default),
+ Teleport only trusts self-signed databases with TLS certificates
+ signed by Teleport's Database Server CA or the ca_cert specified
+ in this TLS setting. For cloud-hosted databases, Teleport downloads
+ the corresponding required CAs for validation.
+ type: boolean
+ type: object
+ uri:
+ description: URI is the database connection endpoint.
+ type: string
+ type: object
+ status:
+ description: Status defines the observed state of the Teleport resource
+ properties:
+ conditions:
+ description: Conditions represent the latest available observations
+ of an object's state
+ items:
+ description: Condition contains details for one aspect of the current
+ state of this API Resource.
+ properties:
+ lastTransitionTime:
+ description: |-
+ lastTransitionTime is the last time the condition transitioned from one status to another.
+ This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
+ format: date-time
+ type: string
+ message:
+ description: |-
+ message is a human readable message indicating details about the transition.
+ This may be an empty string.
+ maxLength: 32768
+ type: string
+ observedGeneration:
+ description: |-
+ observedGeneration represents the .metadata.generation that the condition was set based upon.
+ For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
+ with respect to the current state of the instance.
+ format: int64
+ minimum: 0
+ type: integer
+ reason:
+ description: |-
+ reason contains a programmatic identifier indicating the reason for the condition's last transition.
+ Producers of specific condition types may define expected values and meanings for this field,
+ and whether the values are considered a guaranteed API.
+ The value should be a CamelCase string.
+ This field may not be empty.
+ maxLength: 1024
+ minLength: 1
+ pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+ type: string
+ status:
+ description: status of the condition, one of True, False, Unknown.
+ enum:
+ - "True"
+ - "False"
+ - Unknown
+ type: string
+ type:
+ description: type of condition in CamelCase or in foo.example.com/CamelCase.
+ maxLength: 316
+ pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+ type: string
+ required:
+ - lastTransitionTime
+ - message
+ - reason
+ - status
+ - type
+ type: object
+ type: array
+ teleportResourceID:
+ format: int64
+ type: integer
+ type: object
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
+status:
+ acceptedNames:
+ kind: ""
+ plural: ""
+ conditions: null
+ storedVersions: null
diff --git a/integrations/operator/controllers/resources/database_test.go b/integrations/operator/controllers/resources/database_test.go
new file mode 100644
index 0000000000000..4ad0700336a20
--- /dev/null
+++ b/integrations/operator/controllers/resources/database_test.go
@@ -0,0 +1,124 @@
+/*
+ * Teleport
+ * Copyright (C) 2023 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package resources
+
+import (
+ "context"
+ "net"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/gravitational/teleport/api/types"
+ "github.com/gravitational/teleport/integration/helpers"
+ "github.com/gravitational/teleport/lib/auth"
+ "github.com/gravitational/teleport/lib/defaults"
+ "github.com/gravitational/teleport/lib/modules"
+ "github.com/gravitational/teleport/lib/service/servicecfg"
+ "github.com/gravitational/teleport/lib/services"
+ "github.com/gravitational/teleport/lib/srv/db/common"
+ "github.com/gravitational/teleport/lib/srv/db/postgres"
+)
+
+func startPostgresTestServer(t *testing.T, authServer *auth.Server) *postgres.TestServer {
+ postgresTestServer, err := postgres.NewTestServer(common.TestServerConfig{
+ AuthClient: authServer,
+ })
+ require.NoError(t, err)
+
+ go func() {
+ t.Logf("Postgres Fake server running at %s port", postgresTestServer.Port())
+ assert.NoError(t, postgresTestServer.Serve())
+ }()
+ t.Cleanup(func() {
+ postgresTestServer.Close()
+ })
+
+ return postgresTestServer
+}
+
+func TestDiagnoseConnectionForPostgresDatabases(t *testing.T) {
+ modules.SetInsecureTestMode(true)
+
+ ctx := context.Background()
+
+ // Start Teleport Auth and Proxy services
+ authProcess, proxyProcess, provisionToken := helpers.MakeTestServers(t)
+ authServer := authProcess.GetAuthServer()
+ proxyAddr, err := proxyProcess.ProxyWebAddr()
+ require.NoError(t, err)
+
+ // Start Fake Postgres Database
+ postgresTestServer := startPostgresTestServer(t, authServer)
+
+ // Start Teleport Database Service
+ databaseResourceName := "mypsqldb"
+ databaseDBName := "dbname"
+ databaseDBUser := "dbuser"
+ helpers.MakeTestDatabaseServer(t, *proxyAddr, provisionToken, nil /* resource matchers */, servicecfg.Database{
+ Name: databaseResourceName,
+ Protocol: defaults.ProtocolPostgres,
+ URI: net.JoinHostPort("localhost", postgresTestServer.Port()),
+ })
+ // Wait for the Database Server to be registered
+ waitForDatabases(t, func(ctx context.Context, name string) ([]types.DatabaseServer, error) {
+ return authServer.GetDatabaseServers(ctx, name)
+ }, databaseResourceName)
+
+ roleWithFullAccess, err := types.NewRole("fullaccess", types.RoleSpecV6{
+ Allow: types.RoleConditions{
+ Namespaces: []string{"default"},
+ DatabaseLabels: types.Labels{types.Wildcard: []string{types.Wildcard}},
+ Rules: []types.Rule{
+ types.NewRule(types.KindConnectionDiagnostic, services.RW()),
+ },
+ DatabaseUsers: []string{databaseDBUser},
+ DatabaseNames: []string{databaseDBName},
+ },
+ })
+ require.NoError(t, err)
+ roleWithFullAccess, err = authServer.UpsertRole(ctx, roleWithFullAccess)
+ require.NoError(t, err)
+}
+
+func waitForDatabases(t *testing.T, GetDatabaseServers func(ctx context.Context, name string) ([]types.DatabaseServer, error), dbNames ...string) {
+ ctx := context.Background()
+
+ require.Eventually(t, func() bool {
+ all, err := GetDatabaseServers(ctx, "default")
+ assert.NoError(t, err)
+
+ if len(dbNames) > len(all) {
+ return false
+ }
+
+ registered := 0
+ for _, db := range dbNames {
+ for _, a := range all {
+ if a.GetName() == db {
+ registered++
+ break
+ }
+ }
+ }
+ return registered == len(dbNames)
+ }, 30*time.Second, 100*time.Millisecond)
+}
diff --git a/integrations/operator/controllers/resources/databasev3_controller.go b/integrations/operator/controllers/resources/databasev3_controller.go
new file mode 100644
index 0000000000000..c9a138400e540
--- /dev/null
+++ b/integrations/operator/controllers/resources/databasev3_controller.go
@@ -0,0 +1,80 @@
+/*
+ * Teleport
+ * Copyright (C) 2023 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package resources
+
+import (
+ "context"
+
+ "github.com/gravitational/trace"
+ kclient "sigs.k8s.io/controller-runtime/pkg/client"
+
+ "github.com/gravitational/teleport/api/client"
+ "github.com/gravitational/teleport/api/defaults"
+ "github.com/gravitational/teleport/api/types"
+ resourcesv1 "github.com/gravitational/teleport/integrations/operator/apis/resources/v1"
+ "github.com/gravitational/teleport/integrations/operator/controllers"
+ "github.com/gravitational/teleport/integrations/operator/controllers/reconcilers"
+)
+
+// databaseClient implements TeleportResourceClient and offers CRUD methods
+// needed to reconcile databases.
+type databaseClient struct {
+ teleportClient *client.Client
+}
+
+// Get gets the Teleport database of a given name.
+func (r databaseClient) Get(ctx context.Context, name string) (types.Database, error) {
+ database, err := r.teleportClient.GetDatabase(ctx, name)
+ if err != nil {
+ return database, trace.Wrap(err)
+ }
+ return database, nil
+}
+
+// Create creates a Teleport database.
+func (r databaseClient) Create(ctx context.Context, database types.Database) error {
+ err := r.teleportClient.CreateDatabase(ctx, database)
+ return trace.Wrap(err)
+}
+
+// Update updates a Teleport database.
+func (r databaseClient) Update(ctx context.Context, database types.Database) error {
+ err := r.teleportClient.UpdateDatabase(ctx, database)
+ return trace.Wrap(err)
+}
+
+// Delete deletes a Teleport database.
+func (r databaseClient) Delete(ctx context.Context, name string) error {
+ return trace.Wrap(r.teleportClient.DeleteNode(ctx, defaults.Namespace, name))
+}
+
+// NewDatabaseV3Reconciler instantiates a new Kubernetes controller
+// reconciling database resources.
+func NewDatabaseV3Reconciler(client kclient.Client, tClient *client.Client) (controllers.Reconciler, error) {
+ databaseClient := &databaseClient{
+ teleportClient: tClient,
+ }
+
+ resourceReconciler, err := reconcilers.NewTeleportResourceWithLabelsReconciler[types.Database, *resourcesv1.TeleportDatabaseV3](
+ client,
+ databaseClient,
+ )
+
+ return resourceReconciler, trace.Wrap(err, "building teleport resource reconciler")
+}
diff --git a/integrations/operator/controllers/resources/databasev3_controller_test.go b/integrations/operator/controllers/resources/databasev3_controller_test.go
new file mode 100644
index 0000000000000..b2c480dcdc567
--- /dev/null
+++ b/integrations/operator/controllers/resources/databasev3_controller_test.go
@@ -0,0 +1,124 @@
+/*
+ * Teleport
+ * Copyright (C) 2024 Gravitational, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package resources_test
+
+import (
+ "context"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/gravitational/trace"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ kclient "sigs.k8s.io/controller-runtime/pkg/client"
+
+ "github.com/gravitational/teleport/api/types"
+ resourcesv1 "github.com/gravitational/teleport/integrations/operator/apis/resources/v1"
+ "github.com/gravitational/teleport/integrations/operator/controllers/reconcilers"
+ "github.com/gravitational/teleport/integrations/operator/controllers/resources/testlib"
+)
+
+type databaseV3TestingPrimitives struct {
+ setup *testSetup
+ reconcilers.ResourceWithLabelsAdapter[types.Database]
+}
+
+func (g *databaseV3TestingPrimitives) Init(setup *testSetup) {
+ g.setup = setup
+}
+
+func (g *databaseV3TestingPrimitives) SetupTeleportFixtures(ctx context.Context) error {
+ return nil
+}
+
+func (g *databaseV3TestingPrimitives) CreateTeleportResource(ctx context.Context, name string) error {
+ database, err := types.NewDatabaseV3(types.Metadata{Name: name}, g.setup.DatabaseConfig)
+ if err != nil {
+ return trace.Wrap(err)
+ }
+ database.SetOrigin(types.OriginKubernetes)
+ err = g.setup.TeleportClient.CreateDatabase(ctx, database)
+ return trace.Wrap(err)
+}
+
+func (g *databaseV3TestingPrimitives) GetTeleportResource(ctx context.Context, name string) (types.Database, error) {
+ return g.setup.TeleportClient.GetDatabase(ctx, name)
+}
+
+func (g *databaseV3TestingPrimitives) DeleteTeleportResource(ctx context.Context, name string) error {
+ return trace.Wrap(g.setup.TeleportClient.DeleteDatabase(ctx, name))
+}
+
+func (g *databaseV3TestingPrimitives) CreateKubernetesResource(ctx context.Context, name string) error {
+ database := &resourcesv1.TeleportDatabaseV3{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: name,
+ Namespace: g.setup.Namespace.Name,
+ },
+ Spec: resourcesv1.TeleportDatabaseSpec(g.setup.DatabaseConfig),
+ }
+ return trace.Wrap(g.setup.K8sClient.Create(ctx, database))
+}
+
+func (g *databaseV3TestingPrimitives) DeleteKubernetesResource(ctx context.Context, name string) error {
+ database := &resourcesv1.TeleportDatabaseV3{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: name,
+ Namespace: g.setup.Namespace.Name,
+ },
+ }
+ return trace.Wrap(g.setup.K8sClient.Delete(ctx, database))
+}
+
+func (g *databaseV3TestingPrimitives) GetKubernetesResource(ctx context.Context, name string) (*resourcesv1.TeleportDatabaseV3, error) {
+ database := &resourcesv1.TeleportDatabaseV3{}
+ obj := kclient.ObjectKey{
+ Name: name,
+ Namespace: g.setup.Namespace.Name,
+ }
+ err := g.setup.K8sClient.Get(ctx, obj, database)
+ return database, trace.Wrap(err)
+}
+
+func (g *databaseV3TestingPrimitives) ModifyKubernetesResource(ctx context.Context, name string) error {
+ database, err := g.GetKubernetesResource(ctx, name)
+ if err != nil {
+ return trace.Wrap(err)
+ }
+ return g.setup.K8sClient.Update(ctx, database)
+}
+
+func (g *databaseV3TestingPrimitives) CompareTeleportAndKubernetesResource(tResource types.Database, kubeResource *resourcesv1.TeleportDatabaseV3) (bool, string) {
+ diff := cmp.Diff(tResource, kubeResource.ToTeleport())
+ return diff == "", diff
+}
+
+func TestTeleportDatabaseV3Creation(t *testing.T) {
+ test := &databaseV3TestingPrimitives{}
+ testlib.ResourceCreationTest[types.Database, *resourcesv1.TeleportDatabaseV3](t, test)
+}
+
+func TestTeleportDatabaseV3DeletionDrift(t *testing.T) {
+ test := &databaseV3TestingPrimitives{}
+ testlib.ResourceDeletionDriftTest[types.Database, *resourcesv1.TeleportDatabaseV3](t, test)
+}
+
+func TestTeleportDatabaseV3Update(t *testing.T) {
+ test := &databaseV3TestingPrimitives{}
+ testlib.ResourceUpdateTest[types.Database, *resourcesv1.TeleportDatabaseV3](t, test)
+}
diff --git a/integrations/operator/controllers/resources/github_connector_controller_test.go b/integrations/operator/controllers/resources/github_connector_controller_test.go
deleted file mode 100644
index 2bc074a1825f5..0000000000000
--- a/integrations/operator/controllers/resources/github_connector_controller_test.go
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Teleport
- * Copyright (C) 2023 Gravitational, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package resources_test
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/gravitational/trace"
- "github.com/stretchr/testify/require"
- v1 "k8s.io/api/core/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- kclient "sigs.k8s.io/controller-runtime/pkg/client"
-
- "github.com/gravitational/teleport/api/types"
- resourcesv3 "github.com/gravitational/teleport/integrations/operator/apis/resources/v3"
- "github.com/gravitational/teleport/integrations/operator/controllers/reconcilers"
- "github.com/gravitational/teleport/integrations/operator/controllers/resources/secretlookup"
- "github.com/gravitational/teleport/integrations/operator/controllers/resources/testlib"
-)
-
-var githubSpec = types.GithubConnectorSpecV3{
- ClientID: "client id",
- ClientSecret: "client secret",
- RedirectURL: "https://redirect",
- TeamsToLogins: nil,
- Display: "",
- TeamsToRoles: []types.TeamRolesMapping{{
- Organization: "test",
- Team: "test",
- Roles: []string{"test"},
- }},
-}
-
-type githubTestingPrimitives struct {
- setup *testSetup
- reconcilers.ResourceWithoutLabelsAdapter[types.GithubConnector]
-}
-
-func (g *githubTestingPrimitives) Init(setup *testSetup) {
- g.setup = setup
-}
-
-func (g *githubTestingPrimitives) SetupTeleportFixtures(ctx context.Context) error {
- return nil
-}
-
-func (g *githubTestingPrimitives) CreateTeleportResource(ctx context.Context, name string) error {
- github, err := types.NewGithubConnector(name, githubSpec)
- if err != nil {
- return trace.Wrap(err)
- }
- github.SetOrigin(types.OriginKubernetes)
- _, err = g.setup.TeleportClient.CreateGithubConnector(ctx, github)
- return trace.Wrap(err)
-}
-
-func (g *githubTestingPrimitives) GetTeleportResource(ctx context.Context, name string) (types.GithubConnector, error) {
- return g.setup.TeleportClient.GetGithubConnector(ctx, name, true)
-}
-
-func (g *githubTestingPrimitives) DeleteTeleportResource(ctx context.Context, name string) error {
- return trace.Wrap(g.setup.TeleportClient.DeleteGithubConnector(ctx, name))
-}
-
-func (g *githubTestingPrimitives) CreateKubernetesResource(ctx context.Context, name string) error {
- github := &resourcesv3.TeleportGithubConnector{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- },
- Spec: resourcesv3.TeleportGithubConnectorSpec(githubSpec),
- }
- return trace.Wrap(g.setup.K8sClient.Create(ctx, github))
-}
-
-func (g *githubTestingPrimitives) DeleteKubernetesResource(ctx context.Context, name string) error {
- github := &resourcesv3.TeleportGithubConnector{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- },
- }
- return trace.Wrap(g.setup.K8sClient.Delete(ctx, github))
-}
-
-func (g *githubTestingPrimitives) GetKubernetesResource(ctx context.Context, name string) (*resourcesv3.TeleportGithubConnector, error) {
- github := &resourcesv3.TeleportGithubConnector{}
- obj := kclient.ObjectKey{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- }
- err := g.setup.K8sClient.Get(ctx, obj, github)
- return github, trace.Wrap(err)
-}
-
-func (g *githubTestingPrimitives) ModifyKubernetesResource(ctx context.Context, name string) error {
- github, err := g.GetKubernetesResource(ctx, name)
- if err != nil {
- return trace.Wrap(err)
- }
- github.Spec.TeamsToRoles[0].Roles = []string{"foo", "bar"}
- return trace.Wrap(g.setup.K8sClient.Update(ctx, github))
-}
-
-func (g *githubTestingPrimitives) CompareTeleportAndKubernetesResource(tResource types.GithubConnector, kubeResource *resourcesv3.TeleportGithubConnector) (bool, string) {
- diff := cmp.Diff(tResource, kubeResource.ToTeleport(), testlib.CompareOptions()...)
- return diff == "", diff
-}
-
-func TestGithubConnectorCreation(t *testing.T) {
- test := &githubTestingPrimitives{}
- testlib.ResourceCreationTest[types.GithubConnector, *resourcesv3.TeleportGithubConnector](t, test)
-}
-
-func TestGithubConnectorDeletionDrift(t *testing.T) {
- test := &githubTestingPrimitives{}
- testlib.ResourceDeletionDriftTest[types.GithubConnector, *resourcesv3.TeleportGithubConnector](t, test)
-}
-
-func TestGithubConnectorUpdate(t *testing.T) {
- test := &githubTestingPrimitives{}
- testlib.ResourceUpdateTest[types.GithubConnector, *resourcesv3.TeleportGithubConnector](t, test)
-}
-
-func TestGithubConnectorSecretLookup(t *testing.T) {
- test := &githubTestingPrimitives{}
- setup := testlib.SetupTestEnv(t)
- test.Init(setup)
- ctx := context.Background()
-
- crName := validRandomResourceName("github")
- secretName := validRandomResourceName("github-secret")
- secretKey := "client-secret"
- secretValue := validRandomResourceName("secret-value")
-
- secret := &v1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: secretName,
- Namespace: setup.Namespace.Name,
- Annotations: map[string]string{
- secretlookup.AllowLookupAnnotation: crName,
- },
- },
- StringData: map[string]string{
- secretKey: secretValue,
- },
- Type: v1.SecretTypeOpaque,
- }
- kubeClient := setup.K8sClient
- require.NoError(t, kubeClient.Create(ctx, secret))
-
- github := &resourcesv3.TeleportGithubConnector{
- ObjectMeta: metav1.ObjectMeta{
- Name: crName,
- Namespace: setup.Namespace.Name,
- },
- Spec: resourcesv3.TeleportGithubConnectorSpec(githubSpec),
- }
-
- github.Spec.ClientSecret = "secret://" + secretName + "/" + secretKey
-
- require.NoError(t, kubeClient.Create(ctx, github))
-
- testlib.FastEventually(t, func() bool {
- gh, err := test.GetTeleportResource(ctx, crName)
- if err != nil {
- return false
- }
- return gh.GetClientSecret() == secretValue
- })
-}
diff --git a/integrations/operator/controllers/resources/oidc_connector_controller_test.go b/integrations/operator/controllers/resources/oidc_connector_controller_test.go
deleted file mode 100644
index 39359c2704967..0000000000000
--- a/integrations/operator/controllers/resources/oidc_connector_controller_test.go
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Teleport
- * Copyright (C) 2023 Gravitational, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package resources_test
-
-import (
- "context"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/gravitational/trace"
- "github.com/stretchr/testify/require"
- v1 "k8s.io/api/core/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- kclient "sigs.k8s.io/controller-runtime/pkg/client"
-
- "github.com/gravitational/teleport/api/types"
- resourcesv3 "github.com/gravitational/teleport/integrations/operator/apis/resources/v3"
- "github.com/gravitational/teleport/integrations/operator/controllers/reconcilers"
- "github.com/gravitational/teleport/integrations/operator/controllers/resources/secretlookup"
- "github.com/gravitational/teleport/integrations/operator/controllers/resources/testlib"
-)
-
-var oidcSpec = types.OIDCConnectorSpecV3{
- IssuerURL: "https://issuer",
- ClientID: "client id",
- ClientSecret: "client secret",
- ClaimsToRoles: []types.ClaimMapping{{
- Claim: "claim",
- Value: "value",
- Roles: []string{"roleA"},
- }},
- RedirectURLs: []string{"https://redirect"},
- MaxAge: &types.MaxAge{Value: types.Duration(time.Hour)},
-}
-
-type oidcTestingPrimitives struct {
- setup *testSetup
- reconcilers.ResourceWithoutLabelsAdapter[types.OIDCConnector]
-}
-
-func (g *oidcTestingPrimitives) Init(setup *testSetup) {
- g.setup = setup
-}
-
-func (g *oidcTestingPrimitives) SetupTeleportFixtures(ctx context.Context) error {
- return nil
-}
-
-func (g *oidcTestingPrimitives) CreateTeleportResource(ctx context.Context, name string) error {
- oidc, err := types.NewOIDCConnector(name, oidcSpec)
- if err != nil {
- return trace.Wrap(err)
- }
- oidc.SetOrigin(types.OriginKubernetes)
- _, err = g.setup.TeleportClient.CreateOIDCConnector(ctx, oidc)
- return trace.Wrap(err)
-}
-
-func (g *oidcTestingPrimitives) GetTeleportResource(ctx context.Context, name string) (types.OIDCConnector, error) {
- return g.setup.TeleportClient.GetOIDCConnector(ctx, name, true)
-}
-
-func (g *oidcTestingPrimitives) DeleteTeleportResource(ctx context.Context, name string) error {
- return trace.Wrap(g.setup.TeleportClient.DeleteOIDCConnector(ctx, name))
-}
-
-func (g *oidcTestingPrimitives) CreateKubernetesResource(ctx context.Context, name string) error {
- oidc := &resourcesv3.TeleportOIDCConnector{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- },
- Spec: resourcesv3.TeleportOIDCConnectorSpec(oidcSpec),
- }
- return trace.Wrap(g.setup.K8sClient.Create(ctx, oidc))
-}
-
-func (g *oidcTestingPrimitives) DeleteKubernetesResource(ctx context.Context, name string) error {
- oidc := &resourcesv3.TeleportOIDCConnector{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- },
- }
- return trace.Wrap(g.setup.K8sClient.Delete(ctx, oidc))
-}
-
-func (g *oidcTestingPrimitives) GetKubernetesResource(ctx context.Context, name string) (*resourcesv3.TeleportOIDCConnector, error) {
- oidc := &resourcesv3.TeleportOIDCConnector{}
- obj := kclient.ObjectKey{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- }
- err := g.setup.K8sClient.Get(ctx, obj, oidc)
- return oidc, trace.Wrap(err)
-}
-
-func (g *oidcTestingPrimitives) ModifyKubernetesResource(ctx context.Context, name string) error {
- oidc, err := g.GetKubernetesResource(ctx, name)
- if err != nil {
- return trace.Wrap(err)
- }
- oidc.Spec.RedirectURLs = []string{"https://redirect1", "https://redirect2"}
- return g.setup.K8sClient.Update(ctx, oidc)
-}
-
-func (g *oidcTestingPrimitives) CompareTeleportAndKubernetesResource(tResource types.OIDCConnector, kubeResource *resourcesv3.TeleportOIDCConnector) (bool, string) {
- diff := cmp.Diff(tResource, kubeResource.ToTeleport(), testlib.CompareOptions()...)
- return diff == "", diff
-}
-
-func TestOIDCConnectorCreation(t *testing.T) {
- test := &oidcTestingPrimitives{}
- testlib.ResourceCreationTest[types.OIDCConnector, *resourcesv3.TeleportOIDCConnector](t, test)
-}
-
-func TestOIDCConnectorDeletionDrift(t *testing.T) {
- test := &oidcTestingPrimitives{}
- testlib.ResourceDeletionDriftTest[types.OIDCConnector, *resourcesv3.TeleportOIDCConnector](t, test)
-}
-
-func TestOIDCConnectorUpdate(t *testing.T) {
- test := &oidcTestingPrimitives{}
- testlib.ResourceUpdateTest[types.OIDCConnector, *resourcesv3.TeleportOIDCConnector](t, test)
-}
-
-func TestOIDCConnectorSecretLookup(t *testing.T) {
- test := &oidcTestingPrimitives{}
- setup := testlib.SetupTestEnv(t)
- test.Init(setup)
- ctx := context.Background()
-
- crName := validRandomResourceName("oidc")
- secretName := validRandomResourceName("oidc-secret")
- secretKey := "client-secret"
- secretValue := validRandomResourceName("secret-value")
-
- secret := &v1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: secretName,
- Namespace: setup.Namespace.Name,
- Annotations: map[string]string{
- secretlookup.AllowLookupAnnotation: crName,
- },
- },
- StringData: map[string]string{
- secretKey: secretValue,
- },
- Type: v1.SecretTypeOpaque,
- }
- kubeClient := setup.K8sClient
- require.NoError(t, kubeClient.Create(ctx, secret))
-
- oidc := &resourcesv3.TeleportOIDCConnector{
- ObjectMeta: metav1.ObjectMeta{
- Name: crName,
- Namespace: setup.Namespace.Name,
- },
- Spec: resourcesv3.TeleportOIDCConnectorSpec(oidcSpec),
- }
-
- oidc.Spec.ClientSecret = "secret://" + secretName + "/" + secretKey
-
- require.NoError(t, kubeClient.Create(ctx, oidc))
-
- testlib.FastEventually(t, func() bool {
- oidc, err := test.GetTeleportResource(ctx, crName)
- if err != nil {
- return false
- }
- return oidc.GetClientSecret() == secretValue
- })
-}
diff --git a/integrations/operator/controllers/resources/okta_import_rule_controller_test.go b/integrations/operator/controllers/resources/okta_import_rule_controller_test.go
deleted file mode 100644
index 6e5d852c8ee1e..0000000000000
--- a/integrations/operator/controllers/resources/okta_import_rule_controller_test.go
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Teleport
- * Copyright (C) 2023 Gravitational, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package resources_test
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/gravitational/trace"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- kclient "sigs.k8s.io/controller-runtime/pkg/client"
-
- "github.com/gravitational/teleport/api/types"
- resourcesv1 "github.com/gravitational/teleport/integrations/operator/apis/resources/v1"
- "github.com/gravitational/teleport/integrations/operator/controllers/reconcilers"
- "github.com/gravitational/teleport/integrations/operator/controllers/resources/testlib"
- "github.com/gravitational/teleport/lib/utils"
-)
-
-var oktaImportRuleSpec = types.OktaImportRuleSpecV1{
- Priority: 100,
- Mappings: []*types.OktaImportRuleMappingV1{
- {
- Match: []*types.OktaImportRuleMatchV1{
- {
- AppIDs: []string{"1", "2", "3"},
- },
- },
- AddLabels: map[string]string{
- "label1": "value1",
- },
- },
- {
- Match: []*types.OktaImportRuleMatchV1{
- {
- GroupIDs: []string{"1", "2", "3"},
- },
- },
- AddLabels: map[string]string{
- "label2": "value2",
- },
- },
- },
-}
-
-type oktaImportRuleTestingPrimitives struct {
- setup *testSetup
- reconcilers.ResourceWithLabelsAdapter[types.OktaImportRule]
-}
-
-func (g *oktaImportRuleTestingPrimitives) Init(setup *testSetup) {
- g.setup = setup
-}
-
-func (g *oktaImportRuleTestingPrimitives) SetupTeleportFixtures(ctx context.Context) error {
- return nil
-}
-
-func (g *oktaImportRuleTestingPrimitives) CreateTeleportResource(ctx context.Context, name string) error {
- importRule, err := types.NewOktaImportRule(types.Metadata{
- Name: name,
- }, oktaImportRuleSpec)
- if err != nil {
- return trace.Wrap(err)
- }
- importRule.SetOrigin(types.OriginKubernetes)
- _, err = g.setup.TeleportClient.OktaClient().CreateOktaImportRule(ctx, importRule)
- return trace.Wrap(err)
-}
-
-func (g *oktaImportRuleTestingPrimitives) GetTeleportResource(ctx context.Context, name string) (types.OktaImportRule, error) {
- return g.setup.TeleportClient.OktaClient().GetOktaImportRule(ctx, name)
-}
-
-func (g *oktaImportRuleTestingPrimitives) DeleteTeleportResource(ctx context.Context, name string) error {
- return trace.Wrap(g.setup.TeleportClient.OktaClient().DeleteOktaImportRule(ctx, name))
-}
-
-func (g *oktaImportRuleTestingPrimitives) CreateKubernetesResource(ctx context.Context, name string) error {
- spec := resourcesv1.TeleportOktaImportRuleSpec{
- Priority: oktaImportRuleSpec.Priority,
- Mappings: make([]resourcesv1.TeleportOktaImportRuleMapping, len(oktaImportRuleSpec.Mappings)),
- }
-
- for i, mapping := range oktaImportRuleSpec.Mappings {
- matches := make([]resourcesv1.TeleportOktaImportRuleMatch, len(mapping.Match))
- for j, match := range mapping.Match {
- matches[j] = resourcesv1.TeleportOktaImportRuleMatch{
- AppIDs: match.AppIDs,
- GroupIDs: match.GroupIDs,
- AppNameRegexes: match.AppNameRegexes,
- GroupNameRegexes: match.GroupNameRegexes,
- }
- }
- spec.Mappings[i] = resourcesv1.TeleportOktaImportRuleMapping{
- Match: matches,
- AddLabels: utils.CopyStringsMap(mapping.AddLabels),
- }
- }
-
- importRule := &resourcesv1.TeleportOktaImportRule{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- },
- Spec: spec,
- }
- return trace.Wrap(g.setup.K8sClient.Create(ctx, importRule))
-}
-
-func (g *oktaImportRuleTestingPrimitives) DeleteKubernetesResource(ctx context.Context, name string) error {
- oidc := &resourcesv1.TeleportOktaImportRule{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- },
- }
- return trace.Wrap(g.setup.K8sClient.Delete(ctx, oidc))
-}
-
-func (g *oktaImportRuleTestingPrimitives) GetKubernetesResource(ctx context.Context, name string) (*resourcesv1.TeleportOktaImportRule, error) {
- importRule := &resourcesv1.TeleportOktaImportRule{}
- obj := kclient.ObjectKey{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- }
- err := g.setup.K8sClient.Get(ctx, obj, importRule)
- return importRule, trace.Wrap(err)
-}
-
-func (g *oktaImportRuleTestingPrimitives) ModifyKubernetesResource(ctx context.Context, name string) error {
- importRule, err := g.GetKubernetesResource(ctx, name)
- if err != nil {
- return trace.Wrap(err)
- }
- importRule.Spec.Priority = 50
- return g.setup.K8sClient.Update(ctx, importRule)
-}
-
-func (g *oktaImportRuleTestingPrimitives) CompareTeleportAndKubernetesResource(tResource types.OktaImportRule, kubeResource *resourcesv1.TeleportOktaImportRule) (bool, string) {
- diff := cmp.Diff(tResource, kubeResource.ToTeleport(), testlib.CompareOptions()...)
- return diff == "", diff
-}
-
-func TestOktaImportRuleCreation(t *testing.T) {
- t.Skip("Skipping test since okta reconsider is not available in OSS")
- test := &oktaImportRuleTestingPrimitives{}
- testlib.ResourceCreationTest[types.OktaImportRule, *resourcesv1.TeleportOktaImportRule](t, test)
-}
-
-func TestOktaImportRuleDeletionDrift(t *testing.T) {
- t.Skip("Skipping test since okta reconsider is not available in OSS")
- test := &oktaImportRuleTestingPrimitives{}
- testlib.ResourceDeletionDriftTest[types.OktaImportRule, *resourcesv1.TeleportOktaImportRule](t, test)
-}
-
-func TestOktaImportRuleUpdate(t *testing.T) {
- t.Skip("Skipping test since okta reconsider is not available in OSS")
- test := &oktaImportRuleTestingPrimitives{}
- testlib.ResourceUpdateTest[types.OktaImportRule, *resourcesv1.TeleportOktaImportRule](t, test)
-}
diff --git a/integrations/operator/controllers/resources/openssheiceserverv2_controller_test.go b/integrations/operator/controllers/resources/openssheiceserverv2_controller_test.go
deleted file mode 100644
index fbfdebdc61774..0000000000000
--- a/integrations/operator/controllers/resources/openssheiceserverv2_controller_test.go
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Teleport
- * Copyright (C) 2024 Gravitational, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package resources_test
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/gravitational/trace"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- kclient "sigs.k8s.io/controller-runtime/pkg/client"
-
- "github.com/gravitational/teleport/api/defaults"
- "github.com/gravitational/teleport/api/types"
- resourcesv1 "github.com/gravitational/teleport/integrations/operator/apis/resources/v1"
- "github.com/gravitational/teleport/integrations/operator/controllers/reconcilers"
- "github.com/gravitational/teleport/integrations/operator/controllers/resources/testlib"
-)
-
-var opensshEICEServerV2Spec = types.ServerSpecV2{
- Addr: "127.0.0.1:22",
- Hostname: "test.local",
- CloudMetadata: &types.CloudMetadata{AWS: &types.AWSInfo{
- AccountID: "123",
- InstanceID: "123",
- Region: "us-east-1",
- VPCID: "123",
- Integration: "foo",
- SubnetID: "123",
- }},
-}
-
-type opensshEICEServerV2TestingPrimitives struct {
- setup *testSetup
- reconcilers.ResourceWithLabelsAdapter[types.Server]
-}
-
-func (g *opensshEICEServerV2TestingPrimitives) Init(setup *testSetup) {
- g.setup = setup
-}
-
-func (g *opensshEICEServerV2TestingPrimitives) SetupTeleportFixtures(ctx context.Context) error {
- return nil
-}
-
-func (g *opensshEICEServerV2TestingPrimitives) CreateTeleportResource(ctx context.Context, name string) error {
- node, err := types.NewNode(name, types.SubKindOpenSSHEICENode, opensshEICEServerV2Spec, nil)
- if err != nil {
- return trace.Wrap(err)
- }
- node.SetOrigin(types.OriginKubernetes)
- _, err = g.setup.TeleportClient.UpsertNode(ctx, node)
- return trace.Wrap(err)
-}
-
-func (g *opensshEICEServerV2TestingPrimitives) GetTeleportResource(ctx context.Context, name string) (types.Server, error) {
- return g.setup.TeleportClient.GetNode(ctx, defaults.Namespace, name)
-}
-
-func (g *opensshEICEServerV2TestingPrimitives) DeleteTeleportResource(ctx context.Context, name string) error {
- return trace.Wrap(g.setup.TeleportClient.DeleteNode(ctx, defaults.Namespace, name))
-}
-
-func (g *opensshEICEServerV2TestingPrimitives) CreateKubernetesResource(ctx context.Context, name string) error {
- node := &resourcesv1.TeleportOpenSSHEICEServerV2{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- },
- Spec: resourcesv1.TeleportOpenSSHEICEServerV2Spec(opensshEICEServerV2Spec),
- }
- return trace.Wrap(g.setup.K8sClient.Create(ctx, node))
-}
-
-func (g *opensshEICEServerV2TestingPrimitives) DeleteKubernetesResource(ctx context.Context, name string) error {
- node := &resourcesv1.TeleportOpenSSHEICEServerV2{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- },
- }
- return trace.Wrap(g.setup.K8sClient.Delete(ctx, node))
-}
-
-func (g *opensshEICEServerV2TestingPrimitives) GetKubernetesResource(ctx context.Context, name string) (*resourcesv1.TeleportOpenSSHEICEServerV2, error) {
- node := &resourcesv1.TeleportOpenSSHEICEServerV2{}
- obj := kclient.ObjectKey{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- }
- err := g.setup.K8sClient.Get(ctx, obj, node)
- return node, trace.Wrap(err)
-}
-
-func (g *opensshEICEServerV2TestingPrimitives) ModifyKubernetesResource(ctx context.Context, name string) error {
- node, err := g.GetKubernetesResource(ctx, name)
- if err != nil {
- return trace.Wrap(err)
- }
- node.Spec.Addr = "127.0.0.1:23"
- return g.setup.K8sClient.Update(ctx, node)
-}
-
-func (g *opensshEICEServerV2TestingPrimitives) CompareTeleportAndKubernetesResource(tResource types.Server, kubeResource *resourcesv1.TeleportOpenSSHEICEServerV2) (bool, string) {
- diff := cmp.Diff(tResource, kubeResource.ToTeleport(), testlib.CompareOptions()...)
- return diff == "", diff
-}
-
-func TestTeleportOpensshEICEServerV2Creation(t *testing.T) {
- test := &opensshEICEServerV2TestingPrimitives{}
- testlib.ResourceCreationTest[types.Server, *resourcesv1.TeleportOpenSSHEICEServerV2](t, test)
-}
-
-func TestTeleportOpensshEICEServerV2DeletionDrift(t *testing.T) {
- test := &opensshEICEServerV2TestingPrimitives{}
- testlib.ResourceDeletionDriftTest[types.Server, *resourcesv1.TeleportOpenSSHEICEServerV2](t, test)
-}
-
-func TestTeleportOpensshEICEServerV2Update(t *testing.T) {
- test := &opensshEICEServerV2TestingPrimitives{}
- testlib.ResourceUpdateTest[types.Server, *resourcesv1.TeleportOpenSSHEICEServerV2](t, test)
-}
diff --git a/integrations/operator/controllers/resources/opensshserverv2_controller_test.go b/integrations/operator/controllers/resources/opensshserverv2_controller_test.go
deleted file mode 100644
index fc0c353ce8e23..0000000000000
--- a/integrations/operator/controllers/resources/opensshserverv2_controller_test.go
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Teleport
- * Copyright (C) 2024 Gravitational, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package resources_test
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/gravitational/trace"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- kclient "sigs.k8s.io/controller-runtime/pkg/client"
-
- "github.com/gravitational/teleport/api/defaults"
- "github.com/gravitational/teleport/api/types"
- resourcesv1 "github.com/gravitational/teleport/integrations/operator/apis/resources/v1"
- "github.com/gravitational/teleport/integrations/operator/controllers/reconcilers"
- "github.com/gravitational/teleport/integrations/operator/controllers/resources/testlib"
-)
-
-var opensshServerV2Spec = types.ServerSpecV2{
- Addr: "127.0.0.1:22",
- Hostname: "test.local",
-}
-
-type opensshServerV2TestingPrimitives struct {
- setup *testSetup
- reconcilers.ResourceWithLabelsAdapter[types.Server]
-}
-
-func (g *opensshServerV2TestingPrimitives) Init(setup *testSetup) {
- g.setup = setup
-}
-
-func (g *opensshServerV2TestingPrimitives) SetupTeleportFixtures(ctx context.Context) error {
- return nil
-}
-
-func (g *opensshServerV2TestingPrimitives) CreateTeleportResource(ctx context.Context, name string) error {
- node, err := types.NewNode(name, types.SubKindOpenSSHNode, opensshServerV2Spec, nil)
- if err != nil {
- return trace.Wrap(err)
- }
- node.SetOrigin(types.OriginKubernetes)
- _, err = g.setup.TeleportClient.UpsertNode(ctx, node)
- return trace.Wrap(err)
-}
-
-func (g *opensshServerV2TestingPrimitives) GetTeleportResource(ctx context.Context, name string) (types.Server, error) {
- return g.setup.TeleportClient.GetNode(ctx, defaults.Namespace, name)
-}
-
-func (g *opensshServerV2TestingPrimitives) DeleteTeleportResource(ctx context.Context, name string) error {
- return trace.Wrap(g.setup.TeleportClient.DeleteNode(ctx, defaults.Namespace, name))
-}
-
-func (g *opensshServerV2TestingPrimitives) CreateKubernetesResource(ctx context.Context, name string) error {
- node := &resourcesv1.TeleportOpenSSHServerV2{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- },
- Spec: resourcesv1.TeleportOpenSSHServerV2Spec(opensshServerV2Spec),
- }
- return trace.Wrap(g.setup.K8sClient.Create(ctx, node))
-}
-
-func (g *opensshServerV2TestingPrimitives) DeleteKubernetesResource(ctx context.Context, name string) error {
- node := &resourcesv1.TeleportOpenSSHServerV2{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- },
- }
- return trace.Wrap(g.setup.K8sClient.Delete(ctx, node))
-}
-
-func (g *opensshServerV2TestingPrimitives) GetKubernetesResource(ctx context.Context, name string) (*resourcesv1.TeleportOpenSSHServerV2, error) {
- node := &resourcesv1.TeleportOpenSSHServerV2{}
- obj := kclient.ObjectKey{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- }
- err := g.setup.K8sClient.Get(ctx, obj, node)
- return node, trace.Wrap(err)
-}
-
-func (g *opensshServerV2TestingPrimitives) ModifyKubernetesResource(ctx context.Context, name string) error {
- node, err := g.GetKubernetesResource(ctx, name)
- if err != nil {
- return trace.Wrap(err)
- }
- node.Spec.Addr = "127.0.0.1:23"
- return g.setup.K8sClient.Update(ctx, node)
-}
-
-func (g *opensshServerV2TestingPrimitives) CompareTeleportAndKubernetesResource(tResource types.Server, kubeResource *resourcesv1.TeleportOpenSSHServerV2) (bool, string) {
- diff := cmp.Diff(tResource, kubeResource.ToTeleport(), testlib.CompareOptions()...)
- return diff == "", diff
-}
-
-func TestTeleportOpensshServerV2Creation(t *testing.T) {
- test := &opensshServerV2TestingPrimitives{}
- testlib.ResourceCreationTest[types.Server, *resourcesv1.TeleportOpenSSHServerV2](t, test)
-}
-
-func TestTeleportOpensshServerV2DeletionDrift(t *testing.T) {
- test := &opensshServerV2TestingPrimitives{}
- testlib.ResourceDeletionDriftTest[types.Server, *resourcesv1.TeleportOpenSSHServerV2](t, test)
-}
-
-func TestTeleportOpensshServerV2Update(t *testing.T) {
- test := &opensshServerV2TestingPrimitives{}
- testlib.ResourceUpdateTest[types.Server, *resourcesv1.TeleportOpenSSHServerV2](t, test)
-}
diff --git a/integrations/operator/controllers/resources/provision_token_controller_test.go b/integrations/operator/controllers/resources/provision_token_controller_test.go
deleted file mode 100644
index 18f5cb2c49c1a..0000000000000
--- a/integrations/operator/controllers/resources/provision_token_controller_test.go
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Teleport
- * Copyright (C) 2023 Gravitational, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package resources_test
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/gravitational/trace"
- "github.com/stretchr/testify/require"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/runtime/schema"
- "k8s.io/apimachinery/pkg/util/yaml"
- kclient "sigs.k8s.io/controller-runtime/pkg/client"
-
- "github.com/gravitational/teleport/api/types"
- resourcesv2 "github.com/gravitational/teleport/integrations/operator/apis/resources/v2"
- "github.com/gravitational/teleport/integrations/operator/controllers/reconcilers"
- "github.com/gravitational/teleport/integrations/operator/controllers/resources/testlib"
-)
-
-var tokenSpec = &types.ProvisionTokenSpecV2{
- Roles: []types.SystemRole{types.RoleNode},
- Allow: []*types.TokenRule{
- {
- AWSAccount: "333333333333",
- AWSARN: "arn:aws:sts::333333333333:assumed-role/teleport-node-role/i-*",
- },
- },
- JoinMethod: types.JoinMethodIAM,
-}
-
-var teleportTokenGVK = schema.GroupVersionKind{
- Group: resourcesv2.GroupVersion.Group,
- Version: resourcesv2.GroupVersion.Version,
- Kind: "TeleportProvisionToken",
-}
-
-// newProvisionTokenFromSpecNoExpire returns a new provision token with the given spec without expiration set.
-func newProvisionTokenFromSpecNoExpire(token string, spec types.ProvisionTokenSpecV2) (types.ProvisionToken, error) {
- t := &types.ProvisionTokenV2{
- Metadata: types.Metadata{
- Name: token,
- },
- Spec: spec,
- }
- if err := t.CheckAndSetDefaults(); err != nil {
- return nil, trace.Wrap(err)
- }
- return t, nil
-}
-
-type tokenTestingPrimitives struct {
- setup *testSetup
- reconcilers.ResourceWithoutLabelsAdapter[types.ProvisionToken]
-}
-
-func (g *tokenTestingPrimitives) Init(setup *testSetup) {
- g.setup = setup
-}
-
-func (g *tokenTestingPrimitives) SetupTeleportFixtures(ctx context.Context) error {
- err := teleportCreateDummyRole(ctx, "testRoleA", g.setup.TeleportClient)
- if err != nil {
- return trace.Wrap(err)
- }
- return trace.Wrap(teleportCreateDummyRole(ctx, "testRoleB", g.setup.TeleportClient))
-}
-
-func (g *tokenTestingPrimitives) CreateTeleportResource(ctx context.Context, name string) error {
- token, err := newProvisionTokenFromSpecNoExpire(name, *tokenSpec)
- if err != nil {
- return trace.Wrap(err)
- }
- token.SetOrigin(types.OriginKubernetes)
- return trace.Wrap(g.setup.TeleportClient.UpsertToken(ctx, token))
-}
-
-func (g *tokenTestingPrimitives) GetTeleportResource(ctx context.Context, name string) (types.ProvisionToken, error) {
- return g.setup.TeleportClient.GetToken(ctx, name)
-}
-
-func (g *tokenTestingPrimitives) DeleteTeleportResource(ctx context.Context, name string) error {
- return trace.Wrap(g.setup.TeleportClient.DeleteToken(ctx, name))
-}
-
-func (g *tokenTestingPrimitives) CreateKubernetesResource(ctx context.Context, name string) error {
- token := &resourcesv2.TeleportProvisionToken{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- },
- Spec: resourcesv2.TeleportProvisionTokenSpec(*tokenSpec),
- }
- return trace.Wrap(g.setup.K8sClient.Create(ctx, token))
-}
-
-func (g *tokenTestingPrimitives) DeleteKubernetesResource(ctx context.Context, name string) error {
- saml := &resourcesv2.TeleportProvisionToken{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- },
- }
- return g.setup.K8sClient.Delete(ctx, saml)
-}
-
-func (g *tokenTestingPrimitives) GetKubernetesResource(ctx context.Context, name string) (*resourcesv2.TeleportProvisionToken, error) {
- saml := &resourcesv2.TeleportProvisionToken{}
- obj := kclient.ObjectKey{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- }
- err := g.setup.K8sClient.Get(ctx, obj, saml)
- return saml, trace.Wrap(err)
-}
-
-func (g *tokenTestingPrimitives) ModifyKubernetesResource(ctx context.Context, name string) error {
- saml, err := g.GetKubernetesResource(ctx, name)
- if err != nil {
- return trace.Wrap(err)
- }
- saml.Spec.Roles = []types.SystemRole{types.RoleNode, types.RoleProxy}
- return trace.Wrap(g.setup.K8sClient.Update(ctx, saml))
-}
-
-func (g *tokenTestingPrimitives) CompareTeleportAndKubernetesResource(tResource types.ProvisionToken, kubeResource *resourcesv2.TeleportProvisionToken) (bool, string) {
- diff := cmp.Diff(tResource, kubeResource.ToTeleport(), testlib.CompareOptions()...)
- return diff == "", diff
-}
-
-func TestProvisionTokenCreation(t *testing.T) {
- test := &tokenTestingPrimitives{}
- testlib.ResourceCreationTest[types.ProvisionToken, *resourcesv2.TeleportProvisionToken](t, test)
-}
-
-func TestProvisionTokenDeletionDrift(t *testing.T) {
- test := &tokenTestingPrimitives{}
- testlib.ResourceDeletionDriftTest[types.ProvisionToken, *resourcesv2.TeleportProvisionToken](t, test)
-}
-
-func TestProvisionTokenUpdate(t *testing.T) {
- test := &tokenTestingPrimitives{}
- testlib.ResourceUpdateTest[types.ProvisionToken, *resourcesv2.TeleportProvisionToken](t, test)
-}
-
-// This test checks the operator can create Token resources in Teleport for a
-// typical GitHub Action MachineID setup: the token allows a bot to join from
-// GitHub Actions.
-//
-// Proto messages for GitHub provision tokens have one
-// specificity: the Rule message is defined as a sub message of
-// ProvisionTokenSpecV2GitHub. this caused several issues in the CRD generation
-// tooling and required its own dedicated test.
-func TestProvisionTokenCreation_GitHubBot(t *testing.T) {
- // Test setup
- ctx := context.Background()
- setup := setupTestEnv(t)
- require.NoError(t, teleportCreateDummyRole(ctx, "a", setup.TeleportClient))
-
- tokenSpecYAML := `
-roles:
- - Bot
-join_method: github
-bot_name: my-bot
-github:
- allow:
- - repository: org/repo
-`
- expectedSpec := &types.ProvisionTokenSpecV2{
- Roles: types.SystemRoles{types.RoleBot},
- JoinMethod: types.JoinMethodGitHub,
- BotName: "my-bot",
- GitHub: &types.ProvisionTokenSpecV2GitHub{Allow: []*types.ProvisionTokenSpecV2GitHub_Rule{{Repository: "org/repo"}}},
- }
-
- // Creating the Kubernetes resource. We are using an untyped client to be able to create invalid resources.
- tokenManifest := map[string]interface{}{}
- err := yaml.Unmarshal([]byte(tokenSpecYAML), &tokenManifest)
- require.NoError(t, err)
-
- tokenName := validRandomResourceName("token-")
-
- obj, err := reconcilers.GetUnstructuredObjectFromGVK(teleportTokenGVK)
- require.NoError(t, err)
- obj.Object["spec"] = tokenManifest
- obj.SetName(tokenName)
- obj.SetNamespace(setup.Namespace.Name)
-
- // Doing the test: we create the TeleportProvisionToken in Kubernetes
- err = setup.K8sClient.Create(ctx, obj)
- require.NoError(t, err)
-
- // Then we wait for the token to be created in Teleport
- fastEventually(t, func() bool {
- tToken, err := setup.TeleportClient.GetToken(ctx, tokenName)
- // If the resource creation should succeed we check the resource was found and validate ownership labels
- if trace.IsNotFound(err) {
- return false
- }
- require.NoError(t, err)
-
- require.Equal(t, tokenName, tToken.GetName())
- require.Contains(t, tToken.GetMetadata().Labels, types.OriginLabel)
- require.Equal(t, types.OriginKubernetes, tToken.GetMetadata().Labels[types.OriginLabel])
- expectedToken := &types.ProvisionTokenV2{
- Metadata: types.Metadata{},
- Spec: *expectedSpec,
- }
- _ = expectedToken.CheckAndSetDefaults()
- compareTokenSpecs(t, expectedToken, tToken)
-
- return true
- })
- // Test Teardown
-
- require.NoError(t, setup.K8sClient.Delete(ctx, obj))
- // We wait for the role deletion in Teleport
- fastEventually(t, func() bool {
- _, err := setup.TeleportClient.GetToken(ctx, tokenName)
- return trace.IsNotFound(err)
- })
-}
-
-func compareTokenSpecs(t *testing.T, expectedUser, actualUser types.ProvisionToken) {
- expected, err := teleportResourceToMap(expectedUser)
- require.NoError(t, err)
- actual, err := teleportResourceToMap(actualUser)
- require.NoError(t, err)
-
- require.Equal(t, expected["spec"], actual["spec"])
-}
diff --git a/integrations/operator/controllers/resources/role_controller_test.go b/integrations/operator/controllers/resources/role_controller_test.go
deleted file mode 100644
index 58c7da48235a7..0000000000000
--- a/integrations/operator/controllers/resources/role_controller_test.go
+++ /dev/null
@@ -1,422 +0,0 @@
-/*
- * Teleport
- * Copyright (C) 2023 Gravitational, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package resources_test
-
-import (
- "context"
- "sort"
- "testing"
-
- "github.com/gravitational/trace"
- "github.com/mitchellh/mapstructure"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- kerrors "k8s.io/apimachinery/pkg/api/errors"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/runtime/schema"
- "k8s.io/apimachinery/pkg/util/yaml"
- "k8s.io/client-go/util/retry"
- kclient "sigs.k8s.io/controller-runtime/pkg/client"
-
- "github.com/gravitational/teleport/api/types"
- apiutils "github.com/gravitational/teleport/api/utils"
- apiresources "github.com/gravitational/teleport/integrations/operator/apis/resources"
- resourcesv5 "github.com/gravitational/teleport/integrations/operator/apis/resources/v5"
- "github.com/gravitational/teleport/integrations/operator/controllers/reconcilers"
-)
-
-var TeleportRoleGVKV5 = schema.GroupVersionKind{
- Group: resourcesv5.GroupVersion.Group,
- Version: resourcesv5.GroupVersion.Version,
- Kind: "TeleportRole",
-}
-
-// When I create or delete a TeleportRole CR in Kubernetes,
-// the corresponding TeleportRole must be created/deleted in Teleport.
-func TestRoleCreation(t *testing.T) {
- ctx := context.Background()
- setup := setupTestEnv(t)
- roleName := validRandomResourceName("role-")
-
- // End of setup, we create the role in Kubernetes
- k8sCreateDummyRole(ctx, t, setup.K8sClient, setup.Namespace.Name, roleName)
-
- var tRole types.Role
- var err error
- // We wait for the role to be created in Teleport
- fastEventually(t, func() bool {
- tRole, err = setup.TeleportClient.GetRole(ctx, roleName)
- return !trace.IsNotFound(err)
- })
- require.NoError(t, err)
-
- // Role should have the same name, and have the Kubernetes origin label
- require.Equal(t, roleName, tRole.GetName())
- require.Contains(t, tRole.GetMetadata().Labels, types.OriginLabel)
- require.Equal(t, types.OriginKubernetes, tRole.GetMetadata().Labels[types.OriginLabel])
-
- // Cleanup and setup, we delete the role in Kubernetes
- k8sDeleteRole(ctx, t, setup.K8sClient, roleName, setup.Namespace.Name)
-
- // We wait for the role to be deleted in Teleport
- fastEventually(t, func() bool {
- _, err := setup.TeleportClient.GetRole(ctx, roleName)
- return trace.IsNotFound(err)
- })
-}
-
-func TestRoleCreationFromYAML(t *testing.T) {
- ctx := context.Background()
- setup := setupTestEnv(t)
- tests := []struct {
- name string
- roleSpecYAML string
- shouldFail bool
- expectedSpec *types.RoleSpecV6
- }{
- {
- name: "Valid login list with integer create_host_user_mode",
- roleSpecYAML: `
-allow:
- logins:
- - ubuntu
- - root
-options:
- create_host_user_mode: 3
-`,
- shouldFail: false,
- expectedSpec: &types.RoleSpecV6{
- Allow: types.RoleConditions{
- Logins: []string{"ubuntu", "root"},
- },
- Options: types.RoleOptions{
- CreateHostUserMode: types.CreateHostUserMode_HOST_USER_MODE_KEEP,
- },
- },
- },
- {
- name: "Valid login list",
- roleSpecYAML: `
-allow:
- logins:
- - ubuntu
- - root
-options:
- create_host_user_mode: keep
-`,
- shouldFail: false,
- expectedSpec: &types.RoleSpecV6{
- Allow: types.RoleConditions{
- Logins: []string{"ubuntu", "root"},
- },
- Options: types.RoleOptions{
- CreateHostUserMode: types.CreateHostUserMode_HOST_USER_MODE_KEEP,
- },
- },
- },
- {
- name: "Valid node_labels wildcard (list version)",
- roleSpecYAML: `
-allow:
- node_labels:
- '*': ['*']
-`,
- shouldFail: false,
- expectedSpec: &types.RoleSpecV6{
- Allow: types.RoleConditions{
- NodeLabels: map[string]apiutils.Strings{
- "*": {"*"},
- },
- },
- },
- },
- {
- name: "Valid node_labels wildcard (string version)",
- roleSpecYAML: `
-allow:
- node_labels:
- '*': '*'
-`,
- shouldFail: false,
- expectedSpec: &types.RoleSpecV6{
- Allow: types.RoleConditions{
- NodeLabels: map[string]apiutils.Strings{
- "*": {"*"},
- },
- },
- },
- },
- {
- name: "Invalid node_labels (label value is integer)",
- roleSpecYAML: `
-allow:
- node_labels:
- 'foo': 1
-`,
- shouldFail: true,
- expectedSpec: nil,
- },
- {
- name: "Invalid node_labels (label value is object)",
- roleSpecYAML: `
-allow:
- node_labels:
- 'foo':
- 'bar': 'baz'
- 'logins':
- - 'ubuntu'
-`,
- shouldFail: true,
- expectedSpec: nil,
- },
- }
-
- for _, tc := range tests {
- tc := tc // capture range variable
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
- // Creating the Kubernetes resource. We are using an untyped client to be able to create invalid resources.
- roleManifest := map[string]interface{}{}
- err := yaml.Unmarshal([]byte(tc.roleSpecYAML), &roleManifest)
- require.NoError(t, err)
-
- roleName := validRandomResourceName("role-")
-
- obj, err := reconcilers.GetUnstructuredObjectFromGVK(TeleportRoleGVKV5)
- require.NoError(t, err)
- obj.Object["spec"] = roleManifest
- obj.SetName(roleName)
- obj.SetNamespace(setup.Namespace.Name)
- err = setup.K8sClient.Create(ctx, obj)
- require.NoError(t, err)
-
- // If failure is expected we should not see the resource in Teleport
- if tc.shouldFail {
- fastEventually(t, func() bool {
- // We check status.Conditions was updated, this means the reconciliation happened
- _ = setup.K8sClient.Get(ctx, kclient.ObjectKey{
- Namespace: setup.Namespace.Name,
- Name: roleName,
- }, obj)
- errorConditions := getRoleStatusConditionError(obj.Object)
- // If there's no error condition, reconciliation has not happened yet
- return len(errorConditions) != 0
- })
- _, err = setup.TeleportClient.GetRole(ctx, roleName)
- require.True(t, trace.IsNotFound(err), "The role should not be created in Teleport")
- } else {
- var tRole types.Role
- // We wait for Teleport resource creation
- fastEventually(t, func() bool {
- tRole, err = setup.TeleportClient.GetRole(ctx, roleName)
- return !trace.IsNotFound(err)
- })
- // If the resource creation should succeed we check the resource was found and validate ownership labels
- require.NoError(t, err)
- require.Equal(t, roleName, tRole.GetName())
- require.Contains(t, tRole.GetMetadata().Labels, types.OriginLabel)
- require.Equal(t, types.OriginKubernetes, tRole.GetMetadata().Labels[types.OriginLabel])
- expectedRole, _ := types.NewRole(roleName, *tc.expectedSpec)
- compareRoleSpecs(t, expectedRole, tRole)
- }
- // Teardown
-
- // The role is deleted in K8S
- k8sDeleteRole(ctx, t, setup.K8sClient, roleName, setup.Namespace.Name)
-
- // We wait for the role deletion in Teleport
- fastEventually(t, func() bool {
- _, err := setup.TeleportClient.GetRole(ctx, roleName)
- return trace.IsNotFound(err)
- })
- })
- }
-}
-
-func compareRoleSpecs(t *testing.T, expectedRole, actualRole types.Role) {
- expected, err := teleportResourceToMap(expectedRole)
- require.NoError(t, err)
- actual, err := teleportResourceToMap(actualRole)
- require.NoError(t, err)
-
- require.Equal(t, expected["spec"], actual["spec"])
-}
-
-// TestRoleDeletionDrift tests how the Kubernetes operator reacts when it is asked to delete a role that was
-// already deleted in Teleport
-func TestRoleDeletionDrift(t *testing.T) {
- // Setup section: start the operator, and create a role
- ctx := context.Background()
- setup := setupTestEnv(t)
- roleName := validRandomResourceName("role-")
-
- // The role is created in K8S
- k8sCreateDummyRole(ctx, t, setup.K8sClient, setup.Namespace.Name, roleName)
-
- var tRole types.Role
- var err error
- fastEventually(t, func() bool {
- tRole, err = setup.TeleportClient.GetRole(ctx, roleName)
- return !trace.IsNotFound(err)
- })
- require.NoError(t, err)
- require.Equal(t, roleName, tRole.GetName())
- require.Contains(t, tRole.GetMetadata().Labels, types.OriginLabel)
- require.Equal(t, types.OriginKubernetes, tRole.GetMetadata().Labels[types.OriginLabel])
-
- // We cause a drift by altering the Teleport resource.
- // To make sure the operator does not reconcile while we're finished we suspend the operator
- setup.StopKubernetesOperator()
-
- err = setup.TeleportClient.DeleteRole(ctx, roleName)
- require.NoError(t, err)
- fastEventually(t, func() bool {
- _, err := setup.TeleportClient.GetRole(ctx, roleName)
- return trace.IsNotFound(err)
- })
-
- // We flag the role for deletion in Kubernetes (it won't be fully remopved until the operator has processed it and removed the finalizer)
- k8sDeleteRole(ctx, t, setup.K8sClient, roleName, setup.Namespace.Name)
-
- // Test section: We resume the operator, it should reconcile and recover from the drift
- setup.StartKubernetesOperator(t)
-
- // The operator should handle the failed Teleport deletion gracefully and unlock the Kubernetes resource deletion
- var k8sRole resourcesv5.TeleportRole
- fastEventually(t, func() bool {
- err = setup.K8sClient.Get(ctx, kclient.ObjectKey{
- Namespace: setup.Namespace.Name,
- Name: roleName,
- }, &k8sRole)
- return kerrors.IsNotFound(err)
- })
-}
-
-func TestRoleUpdate(t *testing.T) {
- ctx := context.Background()
- setup := setupTestEnv(t)
- roleName := validRandomResourceName("role-")
-
- // The role does not exist in K8S
- var r resourcesv5.TeleportRole
- err := setup.K8sClient.Get(ctx, kclient.ObjectKey{
- Namespace: setup.Namespace.Name,
- Name: roleName,
- }, &r)
- require.True(t, kerrors.IsNotFound(err))
-
- require.NoError(t, teleportCreateDummyRole(ctx, roleName, setup.TeleportClient))
-
- // The role is created in K8S
- k8sRole := resourcesv5.TeleportRole{
- ObjectMeta: metav1.ObjectMeta{
- Name: roleName,
- Namespace: setup.Namespace.Name,
- },
- Spec: resourcesv5.TeleportRoleSpec{
- Allow: types.RoleConditions{
- Logins: []string{"x", "z"},
- },
- },
- }
- k8sCreateRole(ctx, t, setup.K8sClient, &k8sRole)
-
- // The role is updated in Teleport
- fastEventuallyWithT(t, func(c *assert.CollectT) {
- tRole, err := setup.TeleportClient.GetRole(ctx, roleName)
- require.NoError(c, err)
-
- // TeleportRole updated with new logins
- logins := tRole.GetLogins(types.Allow)
- sort.Strings(logins)
- assert.ElementsMatch(c, logins, []string{"x", "z"})
- })
-
- // Updating the role in K8S
- // The modification can fail because of a conflict with the resource controller. We retry if that happens.
- var k8sRoleNewVersion resourcesv5.TeleportRole
- err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
- err := setup.K8sClient.Get(ctx, kclient.ObjectKey{
- Namespace: setup.Namespace.Name,
- Name: roleName,
- }, &k8sRoleNewVersion)
- if err != nil {
- return err
- }
-
- k8sRoleNewVersion.Spec.Allow.Logins = append(k8sRoleNewVersion.Spec.Allow.Logins, "admin", "root")
- return setup.K8sClient.Update(ctx, &k8sRoleNewVersion)
- })
- require.NoError(t, err)
-
- // Updates the role in Teleport
- fastEventuallyWithT(t, func(c *assert.CollectT) {
- tRole, err := setup.TeleportClient.GetRole(ctx, roleName)
- require.NoError(c, err)
-
- // TeleportRole updated with new logins
- logins := tRole.GetLogins(types.Allow)
- sort.Strings(logins)
- assert.ElementsMatch(c, logins, []string{"admin", "root", "x", "z"})
- })
-}
-
-func k8sCreateDummyRole(ctx context.Context, t *testing.T, kc kclient.Client, namespace, roleName string) {
- role := resourcesv5.TeleportRole{
- ObjectMeta: metav1.ObjectMeta{
- Name: roleName,
- Namespace: namespace,
- },
- Spec: resourcesv5.TeleportRoleSpec{
- Allow: types.RoleConditions{
- Logins: []string{"a", "b"},
- },
- },
- }
- k8sCreateRole(ctx, t, kc, &role)
-}
-
-func k8sDeleteRole(ctx context.Context, t *testing.T, kc kclient.Client, roleName, namespace string) {
- role := resourcesv5.TeleportRole{
- ObjectMeta: metav1.ObjectMeta{
- Name: roleName,
- Namespace: namespace,
- },
- }
- err := kc.Delete(ctx, &role)
- require.NoError(t, err)
-}
-
-func k8sCreateRole(ctx context.Context, t *testing.T, kc kclient.Client, role *resourcesv5.TeleportRole) {
- err := kc.Create(ctx, role)
- require.NoError(t, err)
-}
-
-func getRoleStatusConditionError(object map[string]interface{}) []metav1.Condition {
- var conditionsWithError []metav1.Condition
- var status apiresources.Status
- _ = mapstructure.Decode(object["status"], &status)
-
- for _, condition := range status.Conditions {
- if condition.Status == metav1.ConditionFalse {
- conditionsWithError = append(conditionsWithError, condition)
- }
- }
- return conditionsWithError
-}
diff --git a/integrations/operator/controllers/resources/rolev6_controller_test.go b/integrations/operator/controllers/resources/rolev6_controller_test.go
deleted file mode 100644
index 35b913cbbb243..0000000000000
--- a/integrations/operator/controllers/resources/rolev6_controller_test.go
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Teleport
- * Copyright (C) 2024 Gravitational, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package resources_test
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/gravitational/trace"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- kclient "sigs.k8s.io/controller-runtime/pkg/client"
-
- "github.com/gravitational/teleport/api/types"
- resourcesv1 "github.com/gravitational/teleport/integrations/operator/apis/resources/v1"
- "github.com/gravitational/teleport/integrations/operator/controllers/reconcilers"
- "github.com/gravitational/teleport/integrations/operator/controllers/resources/testlib"
-)
-
-var roleV6Spec = types.RoleSpecV6{
- Options: types.RoleOptions{
- ForwardAgent: true,
- },
- Allow: types.RoleConditions{
- Logins: []string{"foo"},
- KubernetesLabels: types.Labels{"env": {"dev", "prod"}},
- KubernetesResources: []types.KubernetesResource{
- {
- Kind: "pod",
- Namespace: "monitoring",
- Name: "^prometheus-.*",
- },
- },
- },
- Deny: types.RoleConditions{},
-}
-
-type roleV6TestingPrimitives struct {
- setup *testSetup
- reconcilers.ResourceWithLabelsAdapter[types.Role]
-}
-
-func (g *roleV6TestingPrimitives) Init(setup *testSetup) {
- g.setup = setup
-}
-
-func (g *roleV6TestingPrimitives) SetupTeleportFixtures(ctx context.Context) error {
- return nil
-}
-
-func (g *roleV6TestingPrimitives) CreateTeleportResource(ctx context.Context, name string) error {
- role, err := types.NewRoleWithVersion(name, types.V6, roleV6Spec)
- if err != nil {
- return trace.Wrap(err)
- }
- role.SetOrigin(types.OriginKubernetes)
- _, err = g.setup.TeleportClient.CreateRole(ctx, role)
- return trace.Wrap(err)
-}
-
-func (g *roleV6TestingPrimitives) GetTeleportResource(ctx context.Context, name string) (types.Role, error) {
- return g.setup.TeleportClient.GetRole(ctx, name)
-}
-
-func (g *roleV6TestingPrimitives) DeleteTeleportResource(ctx context.Context, name string) error {
- return trace.Wrap(g.setup.TeleportClient.DeleteRole(ctx, name))
-}
-
-func (g *roleV6TestingPrimitives) CreateKubernetesResource(ctx context.Context, name string) error {
- role := &resourcesv1.TeleportRoleV6{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- },
- Spec: resourcesv1.TeleportRoleV6Spec(roleV6Spec),
- }
- return trace.Wrap(g.setup.K8sClient.Create(ctx, role))
-}
-
-func (g *roleV6TestingPrimitives) DeleteKubernetesResource(ctx context.Context, name string) error {
- role := &resourcesv1.TeleportRoleV6{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- },
- }
- return trace.Wrap(g.setup.K8sClient.Delete(ctx, role))
-}
-
-func (g *roleV6TestingPrimitives) GetKubernetesResource(ctx context.Context, name string) (*resourcesv1.TeleportRoleV6, error) {
- role := &resourcesv1.TeleportRoleV6{}
- obj := kclient.ObjectKey{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- }
- err := g.setup.K8sClient.Get(ctx, obj, role)
- return role, trace.Wrap(err)
-}
-
-func (g *roleV6TestingPrimitives) ModifyKubernetesResource(ctx context.Context, name string) error {
- role, err := g.GetKubernetesResource(ctx, name)
- if err != nil {
- return trace.Wrap(err)
- }
- role.Spec.Allow.Logins = []string{"foo", "bar"}
- return g.setup.K8sClient.Update(ctx, role)
-}
-
-func (g *roleV6TestingPrimitives) CompareTeleportAndKubernetesResource(tResource types.Role, kubeResource *resourcesv1.TeleportRoleV6) (bool, string) {
- ignoreServerSideDefaults := []cmp.Option{
- cmpopts.IgnoreFields(types.RoleSpecV6{}, "Options"),
- cmpopts.IgnoreFields(types.RoleConditions{}, "Namespaces"),
- }
- diff := cmp.Diff(tResource, kubeResource.ToTeleport(), testlib.CompareOptions(ignoreServerSideDefaults...)...)
- return diff == "", diff
-}
-
-func TestTeleportRoleV6Creation(t *testing.T) {
- test := &roleV6TestingPrimitives{}
- testlib.ResourceCreationTest[types.Role, *resourcesv1.TeleportRoleV6](t, test)
-}
-
-func TestTeleportRoleV6DeletionDrift(t *testing.T) {
- test := &roleV6TestingPrimitives{}
- testlib.ResourceDeletionDriftTest[types.Role, *resourcesv1.TeleportRoleV6](t, test)
-}
-
-func TestTeleportRoleV6Update(t *testing.T) {
- test := &roleV6TestingPrimitives{}
- testlib.ResourceUpdateTest[types.Role, *resourcesv1.TeleportRoleV6](t, test)
-}
diff --git a/integrations/operator/controllers/resources/rolev7_controller_test.go b/integrations/operator/controllers/resources/rolev7_controller_test.go
deleted file mode 100644
index c798a00f666d3..0000000000000
--- a/integrations/operator/controllers/resources/rolev7_controller_test.go
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Teleport
- * Copyright (C) 2024 Gravitational, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package resources_test
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/gravitational/trace"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- kclient "sigs.k8s.io/controller-runtime/pkg/client"
-
- "github.com/gravitational/teleport/api/types"
- resourcesv1 "github.com/gravitational/teleport/integrations/operator/apis/resources/v1"
- "github.com/gravitational/teleport/integrations/operator/controllers/reconcilers"
- "github.com/gravitational/teleport/integrations/operator/controllers/resources/testlib"
-)
-
-var roleV7Spec = types.RoleSpecV6{
- Allow: types.RoleConditions{
- Logins: []string{"foo"},
- KubernetesLabels: types.Labels{"env": {"dev", "prod"}},
- KubernetesResources: []types.KubernetesResource{
- {
- Kind: "*",
- Namespace: "monitoring",
- Name: "^prometheus-.*",
- },
- },
- },
- Deny: types.RoleConditions{},
-}
-
-type roleV7TestingPrimitives struct {
- setup *testSetup
- reconcilers.ResourceWithLabelsAdapter[types.Role]
-}
-
-func (g *roleV7TestingPrimitives) Init(setup *testSetup) {
- g.setup = setup
-}
-
-func (g *roleV7TestingPrimitives) SetupTeleportFixtures(ctx context.Context) error {
- return nil
-}
-
-func (g *roleV7TestingPrimitives) CreateTeleportResource(ctx context.Context, name string) error {
- role, err := types.NewRoleWithVersion(name, types.V6, roleV6Spec)
- if err != nil {
- return trace.Wrap(err)
- }
- role.SetOrigin(types.OriginKubernetes)
- _, err = g.setup.TeleportClient.CreateRole(ctx, role)
- return trace.Wrap(err)
-}
-
-func (g *roleV7TestingPrimitives) GetTeleportResource(ctx context.Context, name string) (types.Role, error) {
- return g.setup.TeleportClient.GetRole(ctx, name)
-}
-
-func (g *roleV7TestingPrimitives) DeleteTeleportResource(ctx context.Context, name string) error {
- return trace.Wrap(g.setup.TeleportClient.DeleteRole(ctx, name))
-}
-
-func (g *roleV7TestingPrimitives) CreateKubernetesResource(ctx context.Context, name string) error {
- role := &resourcesv1.TeleportRoleV7{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- },
- Spec: resourcesv1.TeleportRoleV7Spec(roleV7Spec),
- }
- return trace.Wrap(g.setup.K8sClient.Create(ctx, role))
-}
-
-func (g *roleV7TestingPrimitives) DeleteKubernetesResource(ctx context.Context, name string) error {
- role := &resourcesv1.TeleportRoleV7{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- },
- }
- return trace.Wrap(g.setup.K8sClient.Delete(ctx, role))
-}
-
-func (g *roleV7TestingPrimitives) GetKubernetesResource(ctx context.Context, name string) (*resourcesv1.TeleportRoleV7, error) {
- role := &resourcesv1.TeleportRoleV7{}
- obj := kclient.ObjectKey{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- }
- err := g.setup.K8sClient.Get(ctx, obj, role)
- return role, trace.Wrap(err)
-}
-
-func (g *roleV7TestingPrimitives) ModifyKubernetesResource(ctx context.Context, name string) error {
- role, err := g.GetKubernetesResource(ctx, name)
- if err != nil {
- return trace.Wrap(err)
- }
- role.Spec.Allow.Logins = []string{"foo", "bar"}
- return g.setup.K8sClient.Update(ctx, role)
-}
-
-func (g *roleV7TestingPrimitives) CompareTeleportAndKubernetesResource(tResource types.Role, kubeResource *resourcesv1.TeleportRoleV7) (bool, string) {
- ignoreServerSideDefaults := []cmp.Option{
- cmpopts.IgnoreFields(types.RoleSpecV6{}, "Options"),
- cmpopts.IgnoreFields(types.RoleConditions{}, "Namespaces"),
- }
- diff := cmp.Diff(tResource, kubeResource.ToTeleport(), testlib.CompareOptions(ignoreServerSideDefaults...)...)
- return diff == "", diff
-}
-
-func TestTeleportRoleV7Creation(t *testing.T) {
- test := &roleV7TestingPrimitives{}
- testlib.ResourceCreationTest[types.Role, *resourcesv1.TeleportRoleV7](t, test)
-}
-
-func TestTeleportRoleV7DeletionDrift(t *testing.T) {
- test := &roleV7TestingPrimitives{}
- testlib.ResourceDeletionDriftTest[types.Role, *resourcesv1.TeleportRoleV7](t, test)
-}
-
-func TestTeleportRoleV7Update(t *testing.T) {
- test := &roleV7TestingPrimitives{}
- testlib.ResourceUpdateTest[types.Role, *resourcesv1.TeleportRoleV7](t, test)
-}
diff --git a/integrations/operator/controllers/resources/saml_connector_controller_test.go b/integrations/operator/controllers/resources/saml_connector_controller_test.go
deleted file mode 100644
index 7b06111a2e89e..0000000000000
--- a/integrations/operator/controllers/resources/saml_connector_controller_test.go
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Teleport
- * Copyright (C) 2023 Gravitational, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package resources_test
-
-import (
- "context"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/gravitational/trace"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- kclient "sigs.k8s.io/controller-runtime/pkg/client"
-
- "github.com/gravitational/teleport/api/types"
- resourcesv2 "github.com/gravitational/teleport/integrations/operator/apis/resources/v2"
- "github.com/gravitational/teleport/integrations/operator/controllers/reconcilers"
- "github.com/gravitational/teleport/integrations/operator/controllers/resources/testlib"
-)
-
-var samlSpec = &types.SAMLConnectorSpecV2{
- Issuer: "issuer",
- SSO: "sso",
- AssertionConsumerService: "acs",
- Audience: "audience",
- ServiceProviderIssuer: "spi",
- AttributesToRoles: []types.AttributeMapping{{
- Name: "test",
- Value: "test",
- Roles: []string{"testRoleA"},
- }},
-}
-
-type samlTestingPrimitives struct {
- setup *testSetup
- reconcilers.ResourceWithoutLabelsAdapter[types.SAMLConnector]
-}
-
-func (g *samlTestingPrimitives) Init(setup *testSetup) {
- g.setup = setup
-}
-
-func (g *samlTestingPrimitives) SetupTeleportFixtures(ctx context.Context) error {
- err := teleportCreateDummyRole(ctx, "testRoleA", g.setup.TeleportClient)
- if err != nil {
- return trace.Wrap(err)
- }
- return trace.Wrap(teleportCreateDummyRole(ctx, "testRoleB", g.setup.TeleportClient))
-}
-
-func (g *samlTestingPrimitives) CreateTeleportResource(ctx context.Context, name string) error {
- saml, err := types.NewSAMLConnector(name, *samlSpec)
- if err != nil {
- return trace.Wrap(err)
- }
- saml.SetOrigin(types.OriginKubernetes)
- _, err = g.setup.TeleportClient.CreateSAMLConnector(ctx, saml)
- return trace.Wrap(err)
-}
-
-func (g *samlTestingPrimitives) GetTeleportResource(ctx context.Context, name string) (types.SAMLConnector, error) {
- return g.setup.TeleportClient.GetSAMLConnector(ctx, name, false)
-}
-
-func (g *samlTestingPrimitives) DeleteTeleportResource(ctx context.Context, name string) error {
- return trace.Wrap(g.setup.TeleportClient.DeleteSAMLConnector(ctx, name))
-}
-
-func (g *samlTestingPrimitives) CreateKubernetesResource(ctx context.Context, name string) error {
- saml := &resourcesv2.TeleportSAMLConnector{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- },
- Spec: resourcesv2.TeleportSAMLConnectorSpec(*samlSpec),
- }
- return trace.Wrap(g.setup.K8sClient.Create(ctx, saml))
-}
-
-func (g *samlTestingPrimitives) DeleteKubernetesResource(ctx context.Context, name string) error {
- saml := &resourcesv2.TeleportSAMLConnector{
- ObjectMeta: metav1.ObjectMeta{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- },
- }
- return g.setup.K8sClient.Delete(ctx, saml)
-}
-
-func (g *samlTestingPrimitives) GetKubernetesResource(ctx context.Context, name string) (*resourcesv2.TeleportSAMLConnector, error) {
- saml := &resourcesv2.TeleportSAMLConnector{}
- obj := kclient.ObjectKey{
- Name: name,
- Namespace: g.setup.Namespace.Name,
- }
- err := g.setup.K8sClient.Get(ctx, obj, saml)
- return saml, trace.Wrap(err)
-}
-
-func (g *samlTestingPrimitives) ModifyKubernetesResource(ctx context.Context, name string) error {
- saml, err := g.GetKubernetesResource(ctx, name)
- if err != nil {
- return trace.Wrap(err)
- }
- saml.Spec.AttributesToRoles[0].Roles = []string{"testRoleA", "testRoleB"}
- return trace.Wrap(g.setup.K8sClient.Update(ctx, saml))
-}
-
-func (g *samlTestingPrimitives) CompareTeleportAndKubernetesResource(tResource types.SAMLConnector, kubeResource *resourcesv2.TeleportSAMLConnector) (bool, string) {
- opts := testlib.CompareOptions(
- // SigningKeyPair is added server-side, it's expected
- cmpopts.IgnoreFields(types.SAMLConnectorSpecV2{}, "SigningKeyPair"),
- )
- diff := cmp.Diff(tResource, kubeResource.ToTeleport(), opts...)
- return diff == "", diff
-}
-
-func TestSAMLConnectorCreation(t *testing.T) {
- test := &samlTestingPrimitives{}
- testlib.ResourceCreationTest[types.SAMLConnector, *resourcesv2.TeleportSAMLConnector](t, test)
-}
-
-func TestSAMLConnectorDeletionDrift(t *testing.T) {
- test := &samlTestingPrimitives{}
- testlib.ResourceDeletionDriftTest[types.SAMLConnector, *resourcesv2.TeleportSAMLConnector](t, test)
-}
-
-func TestSAMLConnectorUpdate(t *testing.T) {
- test := &samlTestingPrimitives{}
- testlib.ResourceUpdateTest[types.SAMLConnector, *resourcesv2.TeleportSAMLConnector](t, test)
-}
diff --git a/integrations/operator/controllers/resources/setup.go b/integrations/operator/controllers/resources/setup.go
index a2e78a8cdc68c..cf1e7aa523e50 100644
--- a/integrations/operator/controllers/resources/setup.go
+++ b/integrations/operator/controllers/resources/setup.go
@@ -47,6 +47,7 @@ func SetupAllControllers(log logr.Logger, mgr manager.Manager, teleportClient *c
{"TeleportProvisionToken", NewProvisionTokenReconciler},
{"TeleportOpenSSHServerV2", NewOpenSSHServerV2Reconciler},
{"TeleportOpenSSHEICEServerV2", NewOpenSSHEICEServerV2Reconciler},
+ {"TeleportDatabaseV3", NewDatabaseV3Reconciler},
}
oidc := modules.GetProtoEntitlement(features, entitlements.OIDC)
diff --git a/integrations/operator/controllers/resources/testlib/env.go b/integrations/operator/controllers/resources/testlib/env.go
index df04115401b59..65f33508d24ae 100644
--- a/integrations/operator/controllers/resources/testlib/env.go
+++ b/integrations/operator/controllers/resources/testlib/env.go
@@ -20,15 +20,15 @@ package testlib
import (
"context"
+ "crypto/tls"
"math/rand/v2"
+ "net"
"os"
"path/filepath"
"runtime"
"testing"
"time"
- "github.com/google/uuid"
- "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap/zapcore"
@@ -48,7 +48,6 @@ import (
"github.com/gravitational/teleport/api/client"
"github.com/gravitational/teleport/api/types"
- "github.com/gravitational/teleport/entitlements"
"github.com/gravitational/teleport/integration/helpers"
resourcesv1 "github.com/gravitational/teleport/integrations/operator/apis/resources/v1"
resourcesv2 "github.com/gravitational/teleport/integrations/operator/apis/resources/v2"
@@ -56,8 +55,14 @@ import (
resourcesv5 "github.com/gravitational/teleport/integrations/operator/apis/resources/v5"
"github.com/gravitational/teleport/integrations/operator/controllers"
"github.com/gravitational/teleport/integrations/operator/controllers/resources"
+ "github.com/gravitational/teleport/lib/auth"
+ "github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/modules"
"github.com/gravitational/teleport/lib/service/servicecfg"
+ "github.com/gravitational/teleport/lib/services"
+ "github.com/gravitational/teleport/lib/srv/db/common"
+ "github.com/gravitational/teleport/lib/srv/db/postgres"
+ "github.com/gravitational/teleport/lib/utils"
)
// scheme is our own test-specific scheme to avoid using the global
@@ -97,60 +102,138 @@ func ValidRandomResourceName(prefix string) string {
return prefix + string(b)
}
-func defaultTeleportServiceConfig(t *testing.T) (*helpers.TeleInstance, string) {
- modules.SetTestModules(t, &modules.TestModules{
- TestBuildType: modules.BuildEnterprise,
- TestFeatures: modules.Features{
- Entitlements: map[entitlements.EntitlementKind]modules.EntitlementInfo{
- entitlements.OIDC: {Enabled: true},
- entitlements.SAML: {Enabled: true},
- },
- },
+func startPostgresTestServer(t *testing.T, authServer *auth.Server) *postgres.TestServer {
+ postgresTestServer, err := postgres.NewTestServer(common.TestServerConfig{
+ AuthClient: authServer,
})
+ require.NoError(t, err)
- teleportServer := helpers.NewInstance(t, helpers.InstanceConfig{
- ClusterName: "root.example.com",
- HostID: uuid.New().String(),
- NodeName: helpers.Loopback,
- Log: logrus.StandardLogger(),
+ go func() {
+ t.Logf("Postgres Fake server running at %s port", postgresTestServer.Port())
+ assert.NoError(t, postgresTestServer.Serve())
+ }()
+ t.Cleanup(func() {
+ postgresTestServer.Close()
})
- rcConf := servicecfg.MakeDefaultConfig()
- rcConf.DataDir = t.TempDir()
- rcConf.Auth.Enabled = true
- rcConf.Proxy.Enabled = true
- rcConf.Proxy.DisableWebInterface = true
- rcConf.SSH.Enabled = true
- rcConf.Version = "v2"
-
- roleName := ValidRandomResourceName("role-")
- unrestricted := []string{"list", "create", "read", "update", "delete"}
- role, err := types.NewRole(roleName, types.RoleSpecV6{
+ return postgresTestServer
+}
+func doCopyPastedTest(t *testing.T) {
+ modules.SetInsecureTestMode(true)
+
+ ctx := context.Background()
+
+ // Start Teleport Auth and Proxy services
+ authProcess, proxyProcess, provisionToken := helpers.MakeTestServers(t)
+ authServer := authProcess.GetAuthServer()
+ proxyAddr, err := proxyProcess.ProxyWebAddr()
+ require.NoError(t, err)
+
+ // Start Fake Postgres Database
+ postgresTestServer := startPostgresTestServer(t, authServer)
+
+ // Start Teleport Database Service
+ databaseResourceName := "mypsqldb"
+ databaseDBName := "dbname"
+ databaseDBUser := "dbuser"
+ helpers.MakeTestDatabaseServer(t, *proxyAddr, provisionToken, nil /* resource matchers */, servicecfg.Database{
+ Name: databaseResourceName,
+ Protocol: defaults.ProtocolPostgres,
+ URI: net.JoinHostPort("localhost", postgresTestServer.Port()),
+ })
+ // Wait for the Database Server to be registered
+ waitForDatabases(t, func(ctx context.Context, name string) ([]types.DatabaseServer, error) {
+ return authServer.GetDatabaseServers(ctx, name)
+ }, databaseResourceName)
+
+ roleWithFullAccess, err := types.NewRole("fullaccess", types.RoleSpecV6{
Allow: types.RoleConditions{
- // the operator has wildcard noe labs to be able to see them
- // but has no login allowed, so it cannot SSH into them
- NodeLabels: types.Labels{"*": []string{"*"}},
+ Namespaces: []string{"default"},
+ DatabaseLabels: types.Labels{types.Wildcard: []string{types.Wildcard}},
Rules: []types.Rule{
- types.NewRule(types.KindRole, unrestricted),
- types.NewRule(types.KindUser, unrestricted),
- types.NewRule(types.KindAuthConnector, unrestricted),
- types.NewRule(types.KindLoginRule, unrestricted),
- types.NewRule(types.KindToken, unrestricted),
- types.NewRule(types.KindOktaImportRule, unrestricted),
- types.NewRule(types.KindAccessList, unrestricted),
- types.NewRule(types.KindNode, unrestricted),
+ types.NewRule(types.KindConnectionDiagnostic, services.RW()),
},
+ DatabaseUsers: []string{databaseDBUser},
+ DatabaseNames: []string{databaseDBName},
},
})
require.NoError(t, err)
+ roleWithFullAccess, err = authServer.UpsertRole(ctx, roleWithFullAccess)
+ require.NoError(t, err)
+}
- operatorName := ValidRandomResourceName("operator-")
- _ = teleportServer.AddUserWithRole(operatorName, role)
-
- err = teleportServer.CreateEx(t, nil, rcConf)
+func defaultTeleportServiceConfig(t *testing.T) (*helpers.TeleInstance, string) {
+ // modules.SetTestModules(t, &modules.TestModules{
+ // TestBuildType: modules.BuildEnterprise,
+ // TestFeatures: modules.Features{
+ // Entitlements: map[entitlements.EntitlementKind]modules.EntitlementInfo{
+ // entitlements.OIDC: {Enabled: true},
+ // entitlements.SAML: {Enabled: true},
+ // },
+ // },
+ // })
+
+ // teleportServer := helpers.NewInstance(t, helpers.InstanceConfig{
+ // ClusterName: "root.example.com",
+ // HostID: uuid.New().String(),
+ // NodeName: helpers.Loopback,
+ // Log: logrus.StandardLogger(),
+ // })
+
+ // rcConf := servicecfg.MakeDefaultConfig()
+ // rcConf.DataDir = t.TempDir()
+ // rcConf.Auth.Enabled = true
+ // rcConf.Proxy.Enabled = true
+ // rcConf.Proxy.DisableWebInterface = true
+ // rcConf.SSH.Enabled = true
+ // rcConf.Version = "v2"
+
+ // rcConf.Auth.StaticTokens, _ = types.NewStaticTokens(types.StaticTokensSpecV2{
+ // StaticTokens: []types.ProvisionTokenV1{{
+ // Roles: []types.SystemRole{types.RoleDatabase},
+ // Expires: time.Now().Add(time.Hour),
+ // Token: "token",
+ // }},
+ // })
+
+ // roleName := ValidRandomResourceName("role-")
+ // unrestricted := []string{"list", "create", "read", "update", "delete"}
+ // role, err := types.NewRole(roleName, types.RoleSpecV6{
+ // Allow: types.RoleConditions{
+ // // the operator has wildcard node labs to be able to see them
+ // // but has no login allowed, so it cannot SSH into them
+ // NodeLabels: types.Labels{"*": []string{"*"}},
+ // Rules: []types.Rule{
+ // types.NewRule(types.KindRole, unrestricted),
+ // types.NewRule(types.KindUser, unrestricted),
+ // types.NewRule(types.KindAuthConnector, unrestricted),
+ // types.NewRule(types.KindLoginRule, unrestricted),
+ // types.NewRule(types.KindToken, unrestricted),
+ // types.NewRule(types.KindOktaImportRule, unrestricted),
+ // types.NewRule(types.KindAccessList, unrestricted),
+ // types.NewRule(types.KindNode, unrestricted),
+ // types.NewRule(types.KindDatabase, unrestricted),
+ // },
+ // Impersonate: &types.ImpersonateConditions{
+ // Users: []string{"Db"},
+ // Roles: []string{"Db"},
+ // },
+ // },
+ // })
+ // require.NoError(t, err)
+
+ // operatorName := ValidRandomResourceName("operator-")
+ // _ = teleportServer.AddUserWithRole(operatorName, role)
+
+ // err = teleportServer.CreateEx(t, nil, rcConf)
+ // require.NoError(t, err)
+
+ authProcess, proxyProcess, provisionToken := helpers.MakeTestServers(t)
+ authServer := authProcess.GetAuthServer()
+ proxyAddr, err := proxyProcess.ProxyWebAddr()
require.NoError(t, err)
- return teleportServer, operatorName
+ return teleportServer, ValidRandomResourceName("operator-")
}
func FastEventually(t *testing.T, condition func() bool) {
@@ -178,12 +261,14 @@ func clientWithCreds(t *testing.T, authAddr string, creds client.Credentials) *c
type TestSetup struct {
TeleportClient *client.Client
+ TeleportProxyAddr utils.NetAddr
K8sClient kclient.Client
K8sRestConfig *rest.Config
Namespace *core.Namespace
Operator manager.Manager
OperatorCancel context.CancelFunc
OperatorName string
+ DatabaseConfig types.DatabaseSpecV3
stepByStepReconciliation bool
}
@@ -229,6 +314,76 @@ func (s *TestSetup) StopKubernetesOperator() {
s.OperatorCancel()
}
+// Spec matches https://goteleport.com/docs/enroll-resources/database-access/guides/dynamic-registration/
+func setupMockPostgresServer(t *testing.T, setup *TestSetup) {
+ postgresTestServer, err := postgres.NewTestServer(common.TestServerConfig{
+ Name: "db-mock-test",
+ AuthClient: setup.TeleportClient,
+ ClientAuth: tls.RequireAndVerifyClientCert,
+ })
+ require.NoError(t, err)
+
+ go func() {
+ require.NoError(t, postgresTestServer.Serve())
+ t.Logf("Postgres Fake server running at %s port", postgresTestServer.Port())
+ }()
+ t.Cleanup(func() {
+ postgresTestServer.Close()
+ })
+
+ setup.DatabaseConfig = types.DatabaseSpecV3{
+ Protocol: "postgres",
+ // URI: net.JoinHostPort("localhost", "45678"),
+ URI: net.JoinHostPort("localhost", postgresTestServer.Port()),
+ }
+
+ databaseResourceName := "testdb"
+ helpers.MakeTestDatabaseServer(t, setup.TeleportProxyAddr, "token", nil, servicecfg.Database{
+ Name: databaseResourceName,
+ Protocol: defaults.ProtocolPostgres,
+ URI: net.JoinHostPort("localhost", postgresTestServer.Port()),
+ })
+
+ waitForDatabases(t, setup.TeleportClient.GetDatabaseServers, databaseResourceName)
+
+ // server, err := clickhouse.NewTestServer(common.TestServerConfig{
+ // AuthClient: setup.TeleportClient,
+ // }, clickhouse.WithClickHouseHTTPProtocol())
+ // require.NoError(t, err)
+
+ // go server.Serve()
+ // t.Cleanup(func() { server.Close() })
+
+ // setup.DatabaseConfig = types.DatabaseSpecV3{
+ // Protocol: defaults.ProtocolClickHouseHTTP,
+ // URI: fmt.Sprintf("https://%s", net.JoinHostPort("localhost", server.Port())),
+ // }
+}
+
+func waitForDatabases(t *testing.T, GetDatabaseServers func(ctx context.Context, name string) ([]types.DatabaseServer, error), dbNames ...string) {
+ ctx := context.Background()
+
+ require.Eventually(t, func() bool {
+ all, err := GetDatabaseServers(ctx, "default")
+ assert.NoError(t, err)
+
+ if len(dbNames) > len(all) {
+ return false
+ }
+
+ registered := 0
+ for _, db := range dbNames {
+ for _, a := range all {
+ if a.GetName() == db {
+ registered++
+ break
+ }
+ }
+ }
+ return registered == len(dbNames)
+ }, 30*time.Second, 100*time.Millisecond)
+}
+
func setupTeleportClient(t *testing.T, setup *TestSetup) {
// Override teleport client with client to locally connected teleport
// cluster (with default tsh credentials).
@@ -258,6 +413,8 @@ func setupTeleportClient(t *testing.T, setup *TestSetup) {
err := setup.TeleportClient.Close()
require.NoError(t, err)
})
+
+ setup.TeleportProxyAddr = teleportServer.Config.ProxyServer
}
type TestOption func(*TestSetup)
@@ -274,6 +431,8 @@ func StepByStep(setup *TestSetup) {
// SetupTestEnv creates a Kubernetes server, a teleport server and starts the operator
func SetupTestEnv(t *testing.T, opts ...TestOption) *TestSetup {
+ doCopyPastedTest(t)
+
// Hack to get the path of this file in order to find the crd path no matter
// where this is called from.
_, thisFileName, _, _ := runtime.Caller(0)
@@ -310,6 +469,7 @@ func SetupTestEnv(t *testing.T, opts ...TestOption) *TestSetup {
}
setupTeleportClient(t, setup)
+ setupMockPostgresServer(t, setup)
// If the test wants to do step by step reconciliation, we don't start
// an operator in the background.
diff --git a/integrations/operator/controllers/resources/user_controller_test.go b/integrations/operator/controllers/resources/user_controller_test.go
deleted file mode 100644
index 373fcf7df29c3..0000000000000
--- a/integrations/operator/controllers/resources/user_controller_test.go
+++ /dev/null
@@ -1,421 +0,0 @@
-/*
- * Teleport
- * Copyright (C) 2023 Gravitational, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package resources_test
-
-import (
- "context"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "github.com/google/go-cmp/cmp/cmpopts"
- "github.com/gravitational/trace"
- "github.com/mitchellh/mapstructure"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- kerrors "k8s.io/apimachinery/pkg/api/errors"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/runtime/schema"
- "k8s.io/apimachinery/pkg/util/yaml"
- "k8s.io/client-go/util/retry"
- kclient "sigs.k8s.io/controller-runtime/pkg/client"
-
- "github.com/gravitational/teleport/api/types"
- apiresources "github.com/gravitational/teleport/integrations/operator/apis/resources"
- v2 "github.com/gravitational/teleport/integrations/operator/apis/resources/v2"
- "github.com/gravitational/teleport/integrations/operator/controllers/reconcilers"
- "github.com/gravitational/teleport/integrations/operator/controllers/resources/testlib"
-)
-
-const teleportUserKind = "TeleportUser"
-
-var teleportUserGVK = schema.GroupVersionKind{
- Group: v2.GroupVersion.Group,
- Version: v2.GroupVersion.Version,
- Kind: teleportUserKind,
-}
-
-func TestUserCreation(t *testing.T) {
- ctx := context.Background()
- setup := setupTestEnv(t)
- userName := validRandomResourceName("user-")
-
- require.NoError(t, teleportCreateDummyRole(ctx, "a", setup.TeleportClient))
- require.NoError(t, teleportCreateDummyRole(ctx, "b", setup.TeleportClient))
-
- // The user is created in K8S
- k8sCreateDummyUser(ctx, t, setup.K8sClient, setup.Namespace.Name, userName)
-
- var tUser types.User
- var err error
- fastEventually(t, func() bool {
- tUser, err = setup.TeleportClient.GetUser(ctx, userName, false)
- return !trace.IsNotFound(err)
- })
- require.NoError(t, err)
- require.Equal(t, userName, tUser.GetName())
- require.Contains(t, tUser.GetMetadata().Labels, types.OriginLabel)
- require.Equal(t, types.OriginKubernetes, tUser.GetMetadata().Labels[types.OriginLabel])
-
- // The user is deleted in K8S
- k8sDeleteUser(ctx, t, setup.K8sClient, userName, setup.Namespace.Name)
-
- fastEventually(t, func() bool {
- _, err := setup.TeleportClient.GetUser(ctx, userName, false)
- return trace.IsNotFound(err)
- })
-}
-
-func TestUserCreationFromYAML(t *testing.T) {
- ctx := context.Background()
- setup := setupTestEnv(t)
- require.NoError(t, teleportCreateDummyRole(ctx, "a", setup.TeleportClient))
- tests := []struct {
- name string
- userSpecYAML string
- shouldFail bool
- expectedSpec *types.UserSpecV2
- }{
- {
- name: "Valid user without traits",
- userSpecYAML: `
-roles:
- - a
-`,
- shouldFail: false,
- expectedSpec: &types.UserSpecV2{
- Roles: []string{"a"},
- },
- },
- {
- name: "Valid user with trait (list with single element)",
- userSpecYAML: `
-roles:
- - a
-traits:
- 'foo': ['bar']
-`,
- shouldFail: false,
- expectedSpec: &types.UserSpecV2{
- Roles: []string{"a"},
- Traits: map[string][]string{
- "foo": {"bar"},
- },
- },
- },
- {
- name: "Valid user with traits (list with multiple element)",
- userSpecYAML: `
-roles:
- - a
-traits:
- 'foo': ['bar', 'baz']
-`,
- shouldFail: false,
- expectedSpec: &types.UserSpecV2{
- Roles: []string{"a"},
- Traits: map[string][]string{
- "foo": {"bar", "baz"},
- },
- },
- },
- {
- name: "Invalid user with non-existing role",
- userSpecYAML: `
-roles:
- - does-not-exist
-traits:
- 'foo': ['bar', 'baz']
-`,
- shouldFail: true,
- expectedSpec: nil,
- },
- }
-
- for _, tc := range tests {
- tc := tc // capture range variable
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
- // Creating the Kubernetes resource. We are using an untyped client to be able to create invalid resources.
- userManifest := map[string]interface{}{}
- err := yaml.Unmarshal([]byte(tc.userSpecYAML), &userManifest)
- require.NoError(t, err)
-
- userName := validRandomResourceName("user-")
-
- obj, err := reconcilers.GetUnstructuredObjectFromGVK(teleportUserGVK)
- require.NoError(t, err)
- obj.Object["spec"] = userManifest
- obj.SetName(userName)
- obj.SetNamespace(setup.Namespace.Name)
- err = setup.K8sClient.Create(ctx, obj)
- require.NoError(t, err)
-
- // If failure is expected we should not see the resource in Teleport
- if tc.shouldFail {
- fastEventually(t, func() bool {
- // We check status.Conditions was updated, this means the reconciliation happened
- _ = setup.K8sClient.Get(ctx, kclient.ObjectKey{
- Namespace: setup.Namespace.Name,
- Name: userName,
- }, obj)
- errorConditions := getUserStatusConditionError(obj.Object)
- // If there's no error condition, reconciliation has not happened yet
- return len(errorConditions) != 0
- })
- _, err = setup.TeleportClient.GetUser(ctx, userName, false /* withSecrets */)
- require.True(t, trace.IsNotFound(err), "The user should not be created in Teleport")
- } else {
- // We wait for Teleport resource creation
- var tUser types.User
- fastEventually(t, func() bool {
- tUser, err = setup.TeleportClient.GetUser(ctx, userName, false /* withSecrets */)
- // If the resource creation should succeed we check the resource was found and validate ownership labels
- return !trace.IsNotFound(err)
- })
- require.NoError(t, err)
- require.Equal(t, userName, tUser.GetName())
- require.Contains(t, tUser.GetMetadata().Labels, types.OriginLabel)
- require.Equal(t, types.OriginKubernetes, tUser.GetMetadata().Labels[types.OriginLabel])
- require.Equal(t, setup.OperatorName, tUser.GetCreatedBy().User.Name)
- expectedUser := &types.UserV2{
- Metadata: types.Metadata{},
- Spec: *tc.expectedSpec,
- }
- _ = expectedUser.CheckAndSetDefaults()
- compareUserSpecs(t, expectedUser, tUser)
- }
- // Teardown
-
- // The role is deleted in K8S
- k8sDeleteUser(ctx, t, setup.K8sClient, userName, setup.Namespace.Name)
-
- // We wait for the role deletion in Teleport
- fastEventually(t, func() bool {
- _, err := setup.TeleportClient.GetUser(ctx, userName, false /* withSecrets */)
- return trace.IsNotFound(err)
- })
- })
- }
-}
-
-func compareUserSpecs(t *testing.T, expectedUser, actualUser types.User) {
- expected, err := teleportResourceToMap(expectedUser)
- require.NoError(t, err)
- actual, err := teleportResourceToMap(actualUser)
- require.NoError(t, err)
-
- // We don't want compare spec.created_by and metadata as they were tested before and are not 100%
- // managed by the operator
- delete(expected["spec"].(map[string]interface{}), "created_by")
- delete(actual["spec"].(map[string]interface{}), "created_by")
-
- require.Equal(t, expected["spec"], actual["spec"])
-}
-
-// TestUserDeletionDrift tests how the Kubernetes operator reacts when it is asked to delete a user that was
-// already deleted in Teleport
-func TestUserDeletionDrift(t *testing.T) {
- // Setup section: start the operator, and create a user
- ctx := context.Background()
- setup := setupTestEnv(t)
- userName := validRandomResourceName("user-")
-
- require.NoError(t, teleportCreateDummyRole(ctx, "a", setup.TeleportClient))
- require.NoError(t, teleportCreateDummyRole(ctx, "b", setup.TeleportClient))
-
- // The user is created in K8S
- k8sCreateDummyUser(ctx, t, setup.K8sClient, setup.Namespace.Name, userName)
-
- var tUser types.User
- var err error
- fastEventually(t, func() bool {
- tUser, err = setup.TeleportClient.GetUser(ctx, userName, false)
- return !trace.IsNotFound(err)
- })
- require.NoError(t, err)
- require.Equal(t, userName, tUser.GetName())
- require.Contains(t, tUser.GetMetadata().Labels, types.OriginLabel)
- require.Equal(t, types.OriginKubernetes, tUser.GetMetadata().Labels[types.OriginLabel])
-
- // We cause a drift by altering the Teleport resource.
- // To make sure the operator does not reconcile while we're finished we suspend the operator
- setup.StopKubernetesOperator()
-
- err = setup.TeleportClient.DeleteUser(ctx, userName)
- require.NoError(t, err)
- fastEventually(t, func() bool {
- _, err := setup.TeleportClient.GetUser(ctx, userName, false)
- return trace.IsNotFound(err)
- })
-
- // We flag the role for deletion in Kubernetes (it won't be fully removed until the operator has processed it and removed the finalizer)
- k8sDeleteUser(ctx, t, setup.K8sClient, userName, setup.Namespace.Name)
-
- // Test section: We resume the operator, it should reconcile and recover from the drift
- setup.StartKubernetesOperator(t)
-
- // The operator should handle the failed Teleport deletion gracefully and unlock the Kubernetes resource deletion
- var k8sUser v2.TeleportUser
- fastEventually(t, func() bool {
- err = setup.K8sClient.Get(ctx, kclient.ObjectKey{
- Namespace: setup.Namespace.Name,
- Name: userName,
- }, &k8sUser)
- return kerrors.IsNotFound(err)
- })
-}
-
-func TestUserUpdate(t *testing.T) {
- ctx := context.Background()
- setup := setupTestEnv(t)
- require.NoError(t, teleportCreateDummyRole(ctx, "a", setup.TeleportClient))
- require.NoError(t, teleportCreateDummyRole(ctx, "b", setup.TeleportClient))
- require.NoError(t, teleportCreateDummyRole(ctx, "x", setup.TeleportClient))
- require.NoError(t, teleportCreateDummyRole(ctx, "y", setup.TeleportClient))
- require.NoError(t, teleportCreateDummyRole(ctx, "z", setup.TeleportClient))
-
- userName := validRandomResourceName("user-")
-
- // The user does not exist in K8S
- var r v2.TeleportUser
- err := setup.K8sClient.Get(ctx, kclient.ObjectKey{
- Namespace: setup.Namespace.Name,
- Name: userName,
- }, &r)
- require.True(t, kerrors.IsNotFound(err))
-
- // The user is created in Teleport
- tUser, err := types.NewUser(userName)
- require.NoError(t, err)
- tUser.SetRoles([]string{"a", "b"})
- metadata := tUser.GetMetadata()
- metadata.Labels = map[string]string{types.OriginLabel: types.OriginKubernetes}
- tUser.SetMetadata(metadata)
- createdBy := types.CreatedBy{
- Connector: nil,
- Time: time.Now(),
- User: types.UserRef{
- Name: setup.OperatorName,
- },
- }
- tUser.SetCreatedBy(createdBy)
-
- tUser, err = setup.TeleportClient.CreateUser(ctx, tUser)
- require.NoError(t, err)
-
- // The user is created in K8S
- k8sUser := v2.TeleportUser{
- ObjectMeta: metav1.ObjectMeta{
- Name: userName,
- Namespace: setup.Namespace.Name,
- },
- Spec: v2.TeleportUserSpec{
- Roles: []string{"x", "z"},
- },
- }
- k8sCreateUser(ctx, t, setup.K8sClient, &k8sUser)
-
- // The user is updated in Teleport
- fastEventually(t, func() bool {
- tUser, err := setup.TeleportClient.GetUser(ctx, userName, false)
- assert.NoError(t, err)
-
- // TeleportUser was updated with new roles
- return compareRoles([]string{"x", "z"}, tUser.GetRoles())
- })
-
- // Updating the user in K8S
- // The modification can fail because of a conflict with the resource controller. We retry if that happens.
- var k8sUserNewVersion v2.TeleportUser
- err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
- err := setup.K8sClient.Get(ctx, kclient.ObjectKey{
- Namespace: setup.Namespace.Name,
- Name: userName,
- }, &k8sUserNewVersion)
- if err != nil {
- return err
- }
-
- k8sUserNewVersion.Spec.Roles = append(k8sUserNewVersion.Spec.Roles, "y")
- return setup.K8sClient.Update(ctx, &k8sUserNewVersion)
- })
- require.NoError(t, err)
-
- // Updates the user in Teleport
- fastEventuallyWithT(t, func(c *assert.CollectT) {
- tUser, err := setup.TeleportClient.GetUser(ctx, userName, false)
- require.NoError(c, err)
-
- // TeleportUser updated with new roles
- assert.ElementsMatch(c, tUser.GetRoles(), []string{"x", "z", "y"})
- })
- require.Equal(t, setup.OperatorName, tUser.GetCreatedBy().User.Name, "createdBy has not been erased")
-}
-
-func k8sCreateDummyUser(ctx context.Context, t *testing.T, kc kclient.Client, namespace, userName string) {
- user := v2.TeleportUser{
- ObjectMeta: metav1.ObjectMeta{
- Name: userName,
- Namespace: namespace,
- },
- Spec: v2.TeleportUserSpec{
- Roles: []string{"a", "b"},
- },
- }
- k8sCreateUser(ctx, t, kc, &user)
-}
-
-func k8sDeleteUser(ctx context.Context, t *testing.T, kc kclient.Client, userName, namespace string) {
- user := v2.TeleportUser{
- ObjectMeta: metav1.ObjectMeta{
- Name: userName,
- Namespace: namespace,
- },
- }
- err := kc.Delete(ctx, &user)
- require.NoError(t, err)
-}
-
-func k8sCreateUser(ctx context.Context, t *testing.T, kc kclient.Client, user *v2.TeleportUser) {
- err := kc.Create(ctx, user)
- require.NoError(t, err)
-}
-
-func getUserStatusConditionError(object map[string]interface{}) []metav1.Condition {
- var conditionsWithError []metav1.Condition
- var status apiresources.Status
- _ = mapstructure.Decode(object["status"], &status)
-
- for _, condition := range status.Conditions {
- if condition.Status == metav1.ConditionFalse {
- conditionsWithError = append(conditionsWithError, condition)
- }
- }
- return conditionsWithError
-}
-
-func compareRoles(expected, actual []string) bool {
- opts := testlib.CompareOptions(cmpopts.SortSlices(func(a, b string) bool { return a < b }))
- return cmp.Diff(
- expected,
- actual,
- opts...,
- ) == ""
-}
diff --git a/integrations/operator/crdgen/handlerequest.go b/integrations/operator/crdgen/handlerequest.go
index 669211d76e3bd..3000b0738e642 100644
--- a/integrations/operator/crdgen/handlerequest.go
+++ b/integrations/operator/crdgen/handlerequest.go
@@ -214,6 +214,13 @@ func generateSchema(file *File, groupName string, format crdFormatFunc, resp *go
withAdditionalColumns(serverColumns),
},
},
+ {
+ name: "DatabaseV3",
+ opts: []resourceSchemaOption{
+ withNameOverride("Database"),
+ withVersionInKindOverride(),
+ },
+ },
}
for _, resource := range resources {
diff --git a/integrations/operator/crdgen/testdata/golden/resources.teleport.dev_databasesv3.yaml b/integrations/operator/crdgen/testdata/golden/resources.teleport.dev_databasesv3.yaml
new file mode 100644
index 0000000000000..96d97f36570b9
--- /dev/null
+++ b/integrations/operator/crdgen/testdata/golden/resources.teleport.dev_databasesv3.yaml
@@ -0,0 +1,456 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ creationTimestamp: null
+ name: teleportdatabasesv3.resources.teleport.dev
+spec:
+ group: resources.teleport.dev
+ names:
+ kind: TeleportDatabaseV3
+ listKind: TeleportDatabaseV3List
+ plural: teleportdatabasesv3
+ shortNames:
+ - databasev3
+ - databasesv3
+ singular: teleportdatabasev3
+ scope: Namespaced
+ versions:
+ - name: v1
+ schema:
+ openAPIV3Schema:
+ description: DatabaseV3 is the Schema for the databasesv3 API
+ properties:
+ apiVersion:
+ description: 'APIVersion defines the versioned schema of this representation
+ of an object. Servers should convert recognized schemas to the latest
+ internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+ type: string
+ kind:
+ description: 'Kind is a string value representing the REST resource this
+ object represents. Servers may infer this from the endpoint the client
+ submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ metadata:
+ type: object
+ spec:
+ description: Database resource definition v3 from Teleport
+ properties:
+ ad:
+ description: AD is the Active Directory configuration for the database.
+ properties:
+ domain:
+ description: Domain is the Active Directory domain the database
+ resides in.
+ type: string
+ kdc_host_name:
+ description: KDCHostName is the host name for a KDC for x509 Authentication.
+ type: string
+ keytab_file:
+ description: KeytabFile is the path to the Kerberos keytab file.
+ type: string
+ krb5_file:
+ description: Krb5File is the path to the Kerberos configuration
+ file. Defaults to /etc/krb5.conf.
+ type: string
+ ldap_cert:
+ description: LDAPCert is a certificate from Windows LDAP/AD, optional;
+ only for x509 Authentication.
+ type: string
+ spn:
+ description: SPN is the service principal name for the database.
+ type: string
+ type: object
+ admin_user:
+ description: AdminUser is the database admin user for automatic user
+ provisioning.
+ nullable: true
+ properties:
+ default_database:
+ description: DefaultDatabase is the database that the privileged
+ database user logs into by default. Depending on the database
+ type, this database may be used to store procedures or data
+ for managing database users.
+ type: string
+ name:
+ description: Name is the username of the privileged database user.
+ type: string
+ type: object
+ aws:
+ description: AWS contains AWS specific settings for RDS/Aurora/Redshift
+ databases.
+ properties:
+ account_id:
+ description: AccountID is the AWS account ID this database belongs
+ to.
+ type: string
+ assume_role_arn:
+ description: AssumeRoleARN is an optional AWS role ARN to assume
+ when accessing a database. Set this field and ExternalID to
+ enable access across AWS accounts.
+ type: string
+ docdb:
+ description: DocumentDB contains AWS DocumentDB specific metadata.
+ properties:
+ cluster_id:
+ description: ClusterID is the cluster identifier.
+ type: string
+ endpoint_type:
+ description: EndpointType is the type of the endpoint.
+ type: string
+ instance_id:
+ description: InstanceID is the instance identifier.
+ type: string
+ type: object
+ elasticache:
+ description: ElastiCache contains AWS ElastiCache Redis specific
+ metadata.
+ properties:
+ endpoint_type:
+ description: EndpointType is the type of the endpoint.
+ type: string
+ replication_group_id:
+ description: ReplicationGroupID is the Redis replication group
+ ID.
+ type: string
+ transit_encryption_enabled:
+ description: TransitEncryptionEnabled indicates whether in-transit
+ encryption (TLS) is enabled.
+ type: boolean
+ user_group_ids:
+ description: UserGroupIDs is a list of user group IDs.
+ items:
+ type: string
+ nullable: true
+ type: array
+ type: object
+ external_id:
+ description: ExternalID is an optional AWS external ID used to
+ enable assuming an AWS role across accounts.
+ type: string
+ iam_policy_status:
+ description: 'IAMPolicyStatus indicates whether the IAM Policy
+ is configured properly for database access. If not, the user
+ must update the AWS profile identity to allow access to the
+ Database. Eg for an RDS Database: the underlying AWS profile
+ allows for `rds-db:connect` for the Database.'
+ x-kubernetes-int-or-string: true
+ memorydb:
+ description: MemoryDB contains AWS MemoryDB specific metadata.
+ properties:
+ acl_name:
+ description: ACLName is the name of the ACL associated with
+ the cluster.
+ type: string
+ cluster_name:
+ description: ClusterName is the name of the MemoryDB cluster.
+ type: string
+ endpoint_type:
+ description: EndpointType is the type of the endpoint.
+ type: string
+ tls_enabled:
+ description: TLSEnabled indicates whether in-transit encryption
+ (TLS) is enabled.
+ type: boolean
+ type: object
+ opensearch:
+ description: OpenSearch contains AWS OpenSearch specific metadata.
+ properties:
+ domain_id:
+ description: DomainID is the ID of the domain.
+ type: string
+ domain_name:
+ description: DomainName is the name of the domain.
+ type: string
+ endpoint_type:
+ description: EndpointType is the type of the endpoint.
+ type: string
+ type: object
+ rds:
+ description: RDS contains RDS specific metadata.
+ properties:
+ cluster_id:
+ description: ClusterID is the RDS cluster (Aurora) identifier.
+ type: string
+ iam_auth:
+ description: IAMAuth indicates whether database IAM authentication
+ is enabled.
+ type: boolean
+ instance_id:
+ description: InstanceID is the RDS instance identifier.
+ type: string
+ resource_id:
+ description: ResourceID is the RDS instance resource identifier
+ (db-xxx).
+ type: string
+ security_groups:
+ description: SecurityGroups is a list of attached security
+ groups for the RDS instance.
+ items:
+ type: string
+ nullable: true
+ type: array
+ subnets:
+ description: Subnets is a list of subnets for the RDS instance.
+ items:
+ type: string
+ nullable: true
+ type: array
+ vpc_id:
+ description: VPCID is the VPC where the RDS is running.
+ type: string
+ type: object
+ rdsproxy:
+ description: RDSProxy contains AWS Proxy specific metadata.
+ properties:
+ custom_endpoint_name:
+ description: CustomEndpointName is the identifier of an RDS
+ Proxy custom endpoint.
+ type: string
+ name:
+ description: Name is the identifier of an RDS Proxy.
+ type: string
+ resource_id:
+ description: ResourceID is the RDS instance resource identifier
+ (prx-xxx).
+ type: string
+ type: object
+ redshift:
+ description: Redshift contains Redshift specific metadata.
+ properties:
+ cluster_id:
+ description: ClusterID is the Redshift cluster identifier.
+ type: string
+ type: object
+ redshift_serverless:
+ description: RedshiftServerless contains AWS Redshift Serverless
+ specific metadata.
+ properties:
+ endpoint_name:
+ description: EndpointName is the VPC endpoint name.
+ type: string
+ workgroup_id:
+ description: WorkgroupID is the workgroup ID.
+ type: string
+ workgroup_name:
+ description: WorkgroupName is the workgroup name.
+ type: string
+ type: object
+ region:
+ description: Region is a AWS cloud region.
+ type: string
+ secret_store:
+ description: SecretStore contains secret store configurations.
+ properties:
+ key_prefix:
+ description: KeyPrefix specifies the secret key prefix.
+ type: string
+ kms_key_id:
+ description: KMSKeyID specifies the AWS KMS key for encryption.
+ type: string
+ type: object
+ session_tags:
+ description: SessionTags is a list of AWS STS session tags.
+ nullable: true
+ properties:
+ key:
+ type: string
+ value:
+ type: string
+ type: object
+ type: object
+ azure:
+ description: Azure contains Azure specific database metadata.
+ properties:
+ is_flexi_server:
+ description: IsFlexiServer is true if the database is an Azure
+ Flexible server.
+ type: boolean
+ name:
+ description: Name is the Azure database server name.
+ type: string
+ redis:
+ description: Redis contains Azure Cache for Redis specific database
+ metadata.
+ properties:
+ clustering_policy:
+ description: ClusteringPolicy is the clustering policy for
+ Redis Enterprise.
+ type: string
+ type: object
+ resource_id:
+ description: ResourceID is the Azure fully qualified ID for the
+ resource.
+ type: string
+ type: object
+ ca_cert:
+ description: 'CACert is the PEM-encoded database CA certificate. DEPRECATED:
+ Moved to TLS.CACert. DELETE IN 10.0.'
+ type: string
+ dynamic_labels:
+ description: DynamicLabels is the database dynamic labels.
+ properties:
+ key:
+ type: string
+ value:
+ nullable: true
+ properties:
+ command:
+ description: Command is a command to run
+ items:
+ type: string
+ nullable: true
+ type: array
+ period:
+ description: Period is a time between command runs
+ format: duration
+ type: string
+ result:
+ description: Result captures standard output
+ type: string
+ type: object
+ type: object
+ gcp:
+ description: GCP contains parameters specific to GCP Cloud SQL databases.
+ properties:
+ instance_id:
+ description: InstanceID is the Cloud SQL instance ID.
+ type: string
+ project_id:
+ description: ProjectID is the GCP project ID the Cloud SQL instance
+ resides in.
+ type: string
+ type: object
+ mongo_atlas:
+ description: MongoAtlas contains Atlas metadata about the database.
+ properties:
+ name:
+ description: Name is the Atlas database instance name.
+ type: string
+ type: object
+ mysql:
+ description: MySQL is an additional section with MySQL database options.
+ properties:
+ server_version:
+ description: ServerVersion is the server version reported by DB
+ proxy if the runtime information is not available.
+ type: string
+ type: object
+ oracle:
+ description: Oracle is an additional Oracle configuration options.
+ properties:
+ audit_user:
+ description: AuditUser is the Oracle database user privilege to
+ access internal Oracle audit trail.
+ type: string
+ type: object
+ protocol:
+ description: 'Protocol is the database protocol: postgres, mysql,
+ mongodb, etc.'
+ type: string
+ tls:
+ description: TLS is the TLS configuration used when establishing connection
+ to target database. Allows to provide custom CA cert or override
+ server name.
+ properties:
+ ca_cert:
+ description: CACert is an optional user provided CA certificate
+ used for verifying database TLS connection.
+ type: string
+ mode:
+ description: Mode is a TLS connection mode. 0 is "verify-full";
+ 1 is "verify-ca", 2 is "insecure".
+ x-kubernetes-int-or-string: true
+ server_name:
+ description: ServerName allows to provide custom hostname. This
+ value will override the servername/hostname on a certificate
+ during validation.
+ type: string
+ trust_system_cert_pool:
+ description: TrustSystemCertPool allows Teleport to trust certificate
+ authorities available on the host system. If not set (by default),
+ Teleport only trusts self-signed databases with TLS certificates
+ signed by Teleport's Database Server CA or the ca_cert specified
+ in this TLS setting. For cloud-hosted databases, Teleport downloads
+ the corresponding required CAs for validation.
+ type: boolean
+ type: object
+ uri:
+ description: URI is the database connection endpoint.
+ type: string
+ type: object
+ status:
+ description: Status defines the observed state of the Teleport resource
+ properties:
+ conditions:
+ description: Conditions represent the latest available observations
+ of an object's state
+ items:
+ description: Condition contains details for one aspect of the current
+ state of this API Resource.
+ properties:
+ lastTransitionTime:
+ description: |-
+ lastTransitionTime is the last time the condition transitioned from one status to another.
+ This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
+ format: date-time
+ type: string
+ message:
+ description: |-
+ message is a human readable message indicating details about the transition.
+ This may be an empty string.
+ maxLength: 32768
+ type: string
+ observedGeneration:
+ description: |-
+ observedGeneration represents the .metadata.generation that the condition was set based upon.
+ For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
+ with respect to the current state of the instance.
+ format: int64
+ minimum: 0
+ type: integer
+ reason:
+ description: |-
+ reason contains a programmatic identifier indicating the reason for the condition's last transition.
+ Producers of specific condition types may define expected values and meanings for this field,
+ and whether the values are considered a guaranteed API.
+ The value should be a CamelCase string.
+ This field may not be empty.
+ maxLength: 1024
+ minLength: 1
+ pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
+ type: string
+ status:
+ description: status of the condition, one of True, False, Unknown.
+ enum:
+ - "True"
+ - "False"
+ - Unknown
+ type: string
+ type:
+ description: type of condition in CamelCase or in foo.example.com/CamelCase.
+ maxLength: 316
+ pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
+ type: string
+ required:
+ - lastTransitionTime
+ - message
+ - reason
+ - status
+ - type
+ type: object
+ type: array
+ teleportResourceID:
+ format: int64
+ type: integer
+ type: object
+ type: object
+ served: true
+ storage: true
+ subresources:
+ status: {}
+status:
+ acceptedNames:
+ kind: ""
+ plural: ""
+ conditions: null
+ storedVersions: null
diff --git a/integrations/operator/hack/fixture-operator-role.yaml b/integrations/operator/hack/fixture-operator-role.yaml
index e9925b19a106c..5fd1e6493a98f 100644
--- a/integrations/operator/hack/fixture-operator-role.yaml
+++ b/integrations/operator/hack/fixture-operator-role.yaml
@@ -73,5 +73,18 @@ spec:
- read
- update
- delete
+ - resources:
+ - db
+ verbs:
+ - list
+ - create
+ - read
+ - update
+ - delete
+ impersonate:
+ users:
+ - Db
+ roles:
+ - Db
deny: {}
version: v7
diff --git a/lib/client/conntest/database.go b/lib/client/conntest/database.go
index 40d2a9785c21c..d64c395103650 100644
--- a/lib/client/conntest/database.go
+++ b/lib/client/conntest/database.go
@@ -99,7 +99,7 @@ func NewDatabaseConnectionTester(cfg DatabaseConnectionTesterConfig) (*DatabaseC
// The following checkpoints are reported:
// - database server for the requested database exists / the user's roles can access it
// - the user can use the requested database user and database name (per their roles)
-// - the database is acessible and accepting connections from the database server
+// - the database is accessible and accepting connections from the database server
// - the database has the database user and database name that was requested
func (s *DatabaseConnectionTester) TestConnection(ctx context.Context, req TestConnectionRequest) (types.ConnectionDiagnostic, error) {
if req.ResourceKind != types.KindDatabase {
diff --git a/lib/service/service.go b/lib/service/service.go
index a95dd83820b69..4b08ba6fed4b6 100644
--- a/lib/service/service.go
+++ b/lib/service/service.go
@@ -1306,13 +1306,13 @@ func NewTeleport(cfg *servicecfg.Config) (*TeleportProcess, error) {
}
}
- if cfg.DebugService.Enabled {
- if err := process.initDebugService(); err != nil {
- return nil, trace.Wrap(err)
- }
- } else {
- warnOnErr(process.ExitContext(), process.closeImportedDescriptors(teleport.ComponentDebug), process.logger)
- }
+ // if cfg.DebugService.Enabled {
+ // if err := process.initDebugService(); err != nil {
+ // return nil, trace.Wrap(err)
+ // }
+ // } else {
+ // warnOnErr(process.ExitContext(), process.closeImportedDescriptors(teleport.ComponentDebug), process.logger)
+ // }
// Create a process wide key generator that will be shared. This is so the
// key generator can pre-generate keys and share these across services.
diff --git a/lib/srv/db/postgres/test.go b/lib/srv/db/postgres/test.go
index 955bb39e2c7a2..7a0c13b712cd8 100644
--- a/lib/srv/db/postgres/test.go
+++ b/lib/srv/db/postgres/test.go
@@ -182,23 +182,31 @@ func NewTestServer(config common.TestServerConfig) (svr *TestServer, err error)
// Serve starts serving client connections.
func (s *TestServer) Serve() error {
+ fmt.Printf("PG: Starting test Postgres server. address: %q\n", s.listener.Addr())
s.log.DebugContext(context.Background(), "Starting test Postgres server.", "address", s.listener.Addr())
+ defer fmt.Printf("PG: Test Postgres server stopped.\n")
defer s.log.DebugContext(context.Background(), "Test Postgres server stopped.")
for {
conn, err := s.listener.Accept()
+ fmt.Printf("PG: GOT CONN REQUEST\n")
if err != nil {
if utils.IsOKNetworkError(err) {
+ fmt.Printf("PG: 'IsOKNetworkError: %s\n", trace.DebugReport(err))
return nil
}
+ fmt.Printf("PG: Failed to accept connection. error: %s\n", trace.DebugReport(err))
s.log.ErrorContext(context.Background(), "Failed to accept connection.", "error", err)
continue
}
+ fmt.Printf("PG: Accepted connection.\n")
s.log.DebugContext(context.Background(), "Accepted connection.")
go func() {
+ defer fmt.Printf("PG: Connection done.\n")
defer s.log.DebugContext(context.Background(), "Connection done.")
defer conn.Close()
err = s.handleConnection(conn)
if err != nil {
+ fmt.Printf("PG: Failed to handle connection. debug_report: %s\n", trace.DebugReport(err))
s.log.ErrorContext(context.Background(), "Failed to handle connection.", "debug_report", trace.DebugReport(err))
}
}()