From 7cd88ca8c64f1c6f123fcd35e501ac204525c776 Mon Sep 17 00:00:00 2001 From: Fred Heinecke Date: Thu, 5 Dec 2024 18:00:39 -0600 Subject: [PATCH 1/3] Added Database V3 resource to operator --- .../resources.teleport.dev_databases.yaml | 456 ++++++++++++++++++ .../resources.teleport.dev_databasesv3.yaml | 456 ++++++++++++++++++ .../templates/auth/config.yaml | 13 + .../apis/resources/v1/databasev3_types.go | 95 ++++ .../resources/v1/zz_generated.deepcopy.go | 69 +++ .../resources.teleport.dev_databases.yaml | 456 ++++++++++++++++++ .../resources.teleport.dev_databasesv3.yaml | 456 ++++++++++++++++++ .../resources/databasev3_controller.go | 80 +++ .../resources/databasev3_controller_test.go | 124 +++++ .../operator/controllers/resources/setup.go | 1 + .../controllers/resources/testlib/env.go | 34 +- integrations/operator/crdgen/handlerequest.go | 7 + .../resources.teleport.dev_databasesv3.yaml | 456 ++++++++++++++++++ .../operator/hack/fixture-operator-role.yaml | 13 + 14 files changed, 2715 insertions(+), 1 deletion(-) create mode 100644 examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_databases.yaml create mode 100644 examples/chart/teleport-cluster/charts/teleport-operator/operator-crds/resources.teleport.dev_databasesv3.yaml create mode 100644 integrations/operator/apis/resources/v1/databasev3_types.go create mode 100644 integrations/operator/config/crd/bases/resources.teleport.dev_databases.yaml create mode 100644 integrations/operator/config/crd/bases/resources.teleport.dev_databasesv3.yaml create mode 100644 integrations/operator/controllers/resources/databasev3_controller.go create mode 100644 integrations/operator/controllers/resources/databasev3_controller_test.go create mode 100644 integrations/operator/crdgen/testdata/golden/resources.teleport.dev_databasesv3.yaml 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/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/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/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..eb0e305bfb905 100644 --- a/integrations/operator/controllers/resources/testlib/env.go +++ b/integrations/operator/controllers/resources/testlib/env.go @@ -21,6 +21,7 @@ package testlib import ( "context" "math/rand/v2" + "net" "os" "path/filepath" "runtime" @@ -58,6 +59,8 @@ import ( "github.com/gravitational/teleport/integrations/operator/controllers/resources" "github.com/gravitational/teleport/lib/modules" "github.com/gravitational/teleport/lib/service/servicecfg" + "github.com/gravitational/teleport/lib/srv/db/common" + "github.com/gravitational/teleport/lib/srv/db/postgres" ) // scheme is our own test-specific scheme to avoid using the global @@ -127,7 +130,7 @@ func defaultTeleportServiceConfig(t *testing.T) (*helpers.TeleInstance, string) unrestricted := []string{"list", "create", "read", "update", "delete"} role, err := types.NewRole(roleName, types.RoleSpecV6{ Allow: types.RoleConditions{ - // the operator has wildcard noe labs to be able to see them + // 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{ @@ -139,6 +142,11 @@ func defaultTeleportServiceConfig(t *testing.T) (*helpers.TeleInstance, string) 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"}, }, }, }) @@ -184,6 +192,7 @@ type TestSetup struct { Operator manager.Manager OperatorCancel context.CancelFunc OperatorName string + DatabaseConfig types.DatabaseSpecV3 stepByStepReconciliation bool } @@ -229,6 +238,28 @@ 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{ + AuthClient: setup.TeleportClient, + AllowAnyUser: true, + }) + 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", postgresTestServer.Port()), + } +} + func setupTeleportClient(t *testing.T, setup *TestSetup) { // Override teleport client with client to locally connected teleport // cluster (with default tsh credentials). @@ -310,6 +341,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/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 From 8a0b57fcd94eecb6159dc25c640f1037e59541e0 Mon Sep 17 00:00:00 2001 From: Fred Heinecke Date: Mon, 9 Dec 2024 16:24:09 -0600 Subject: [PATCH 2/3] temp --- integration/helpers/helpers.go | 5 +- integrations/operator/Makefile | 4 +- .../github_connector_controller_test.go | 189 -------- .../oidc_connector_controller_test.go | 190 -------- .../okta_import_rule_controller_test.go | 178 -------- .../openssheiceserverv2_controller_test.go | 139 ------ .../opensshserverv2_controller_test.go | 131 ------ .../provision_token_controller_test.go | 249 ----------- .../resources/role_controller_test.go | 422 ------------------ .../resources/rolev6_controller_test.go | 148 ------ .../resources/rolev7_controller_test.go | 145 ------ .../saml_connector_controller_test.go | 147 ------ .../controllers/resources/testlib/env.go | 69 ++- .../resources/user_controller_test.go | 421 ----------------- lib/client/conntest/database.go | 4 +- lib/service/service.go | 14 +- lib/srv/db/postgres/test.go | 8 + 17 files changed, 89 insertions(+), 2374 deletions(-) delete mode 100644 integrations/operator/controllers/resources/github_connector_controller_test.go delete mode 100644 integrations/operator/controllers/resources/oidc_connector_controller_test.go delete mode 100644 integrations/operator/controllers/resources/okta_import_rule_controller_test.go delete mode 100644 integrations/operator/controllers/resources/openssheiceserverv2_controller_test.go delete mode 100644 integrations/operator/controllers/resources/opensshserverv2_controller_test.go delete mode 100644 integrations/operator/controllers/resources/provision_token_controller_test.go delete mode 100644 integrations/operator/controllers/resources/role_controller_test.go delete mode 100644 integrations/operator/controllers/resources/rolev6_controller_test.go delete mode 100644 integrations/operator/controllers/resources/rolev7_controller_test.go delete mode 100644 integrations/operator/controllers/resources/saml_connector_controller_test.go delete mode 100644 integrations/operator/controllers/resources/user_controller_test.go 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/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/testlib/env.go b/integrations/operator/controllers/resources/testlib/env.go index eb0e305bfb905..8e3277d7d1ccf 100644 --- a/integrations/operator/controllers/resources/testlib/env.go +++ b/integrations/operator/controllers/resources/testlib/env.go @@ -20,6 +20,7 @@ package testlib import ( "context" + "crypto/tls" "math/rand/v2" "net" "os" @@ -57,10 +58,12 @@ 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/defaults" "github.com/gravitational/teleport/lib/modules" "github.com/gravitational/teleport/lib/service/servicecfg" "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 @@ -126,6 +129,14 @@ func defaultTeleportServiceConfig(t *testing.T) (*helpers.TeleInstance, string) 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{ @@ -186,6 +197,7 @@ 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 @@ -241,8 +253,9 @@ func (s *TestSetup) StopKubernetesOperator() { // 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{ - AuthClient: setup.TeleportClient, - AllowAnyUser: true, + Name: "db-mock-test", + AuthClient: setup.TeleportClient, + ClientAuth: tls.RequireAndVerifyClientCert, }) require.NoError(t, err) @@ -256,8 +269,56 @@ func setupMockPostgresServer(t *testing.T, setup *TestSetup) { setup.DatabaseConfig = types.DatabaseSpecV3{ Protocol: "postgres", - URI: net.JoinHostPort("localhost", postgresTestServer.Port()), + // 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, 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, setup *TestSetup, dbNames ...string) { + ctx := context.Background() + + require.Eventually(t, func() bool { + all, err := setup.TeleportClient.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 { + require.FailNow(t, "Got db: %#v", a) + registered++ + break + } + } + } + return registered == len(dbNames) + }, 30*time.Second, 100*time.Millisecond) } func setupTeleportClient(t *testing.T, setup *TestSetup) { @@ -289,6 +350,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) 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/lib/client/conntest/database.go b/lib/client/conntest/database.go index 40d2a9785c21c..76488157518a8 100644 --- a/lib/client/conntest/database.go +++ b/lib/client/conntest/database.go @@ -99,9 +99,10 @@ 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) { + fmt.Printf("HERE TESTCONNECTION *******************************") if req.ResourceKind != types.KindDatabase { return nil, trace.BadParameter("invalid value for ResourceKind, expected %q got %q", types.KindDatabase, req.ResourceKind) } @@ -126,6 +127,7 @@ func (s *DatabaseConnectionTester) TestConnection(ctx context.Context, req TestC return nil, trace.Wrap(err) } + return s.handlePingSuccess(ctx, connectionDiagnosticID) databaseServers, err := s.getDatabaseServers(ctx, req.ResourceName) if err != nil { return nil, trace.Wrap(err) 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)) } }() From 24fa4c10a76741e927bbcee692cd4a4e5fb22dc8 Mon Sep 17 00:00:00 2001 From: Fred Heinecke Date: Wed, 11 Dec 2024 13:50:48 -0600 Subject: [PATCH 3/3] temp --- .../controllers/resources/database_test.go | 124 ++++++++++++ .../controllers/resources/testlib/env.go | 181 ++++++++++++------ lib/client/conntest/database.go | 2 - 3 files changed, 247 insertions(+), 60 deletions(-) create mode 100644 integrations/operator/controllers/resources/database_test.go 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/testlib/env.go b/integrations/operator/controllers/resources/testlib/env.go index 8e3277d7d1ccf..65f33508d24ae 100644 --- a/integrations/operator/controllers/resources/testlib/env.go +++ b/integrations/operator/controllers/resources/testlib/env.go @@ -29,8 +29,6 @@ import ( "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" @@ -50,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" @@ -58,9 +55,11 @@ 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" @@ -103,73 +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" - - rcConf.Auth.StaticTokens, _ = types.NewStaticTokens(types.StaticTokensSpecV2{ - StaticTokens: []types.ProvisionTokenV1{{ - Roles: []types.SystemRole{types.RoleDatabase}, - Expires: time.Now().Add(time.Hour), - Token: "token", - }}, + 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) - roleName := ValidRandomResourceName("role-") - unrestricted := []string{"list", "create", "read", "update", "delete"} - role, err := types.NewRole(roleName, types.RoleSpecV6{ + roleWithFullAccess, err := types.NewRole("fullaccess", 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{"*"}}, + 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.KindDatabase, unrestricted), - }, - Impersonate: &types.ImpersonateConditions{ - Users: []string{"Db"}, - Roles: []string{"Db"}, + 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 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) + // operatorName := ValidRandomResourceName("operator-") + // _ = teleportServer.AddUserWithRole(operatorName, role) - err = teleportServer.CreateEx(t, nil, rcConf) + // 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) { @@ -280,7 +344,7 @@ func setupMockPostgresServer(t *testing.T, setup *TestSetup) { URI: net.JoinHostPort("localhost", postgresTestServer.Port()), }) - waitForDatabases(t, setup, databaseResourceName) + waitForDatabases(t, setup.TeleportClient.GetDatabaseServers, databaseResourceName) // server, err := clickhouse.NewTestServer(common.TestServerConfig{ // AuthClient: setup.TeleportClient, @@ -296,11 +360,11 @@ func setupMockPostgresServer(t *testing.T, setup *TestSetup) { // } } -func waitForDatabases(t *testing.T, setup *TestSetup, dbNames ...string) { +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 := setup.TeleportClient.GetDatabaseServers(ctx, "default") + all, err := GetDatabaseServers(ctx, "default") assert.NoError(t, err) if len(dbNames) > len(all) { @@ -311,7 +375,6 @@ func waitForDatabases(t *testing.T, setup *TestSetup, dbNames ...string) { for _, db := range dbNames { for _, a := range all { if a.GetName() == db { - require.FailNow(t, "Got db: %#v", a) registered++ break } @@ -368,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) diff --git a/lib/client/conntest/database.go b/lib/client/conntest/database.go index 76488157518a8..d64c395103650 100644 --- a/lib/client/conntest/database.go +++ b/lib/client/conntest/database.go @@ -102,7 +102,6 @@ func NewDatabaseConnectionTester(cfg DatabaseConnectionTesterConfig) (*DatabaseC // - 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) { - fmt.Printf("HERE TESTCONNECTION *******************************") if req.ResourceKind != types.KindDatabase { return nil, trace.BadParameter("invalid value for ResourceKind, expected %q got %q", types.KindDatabase, req.ResourceKind) } @@ -127,7 +126,6 @@ func (s *DatabaseConnectionTester) TestConnection(ctx context.Context, req TestC return nil, trace.Wrap(err) } - return s.handlePingSuccess(ctx, connectionDiagnosticID) databaseServers, err := s.getDatabaseServers(ctx, req.ResourceName) if err != nil { return nil, trace.Wrap(err)