From 694c090903c31df8cd41618fb878c44cf372b271 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sat, 9 Mar 2024 01:47:18 +1000 Subject: [PATCH] Fixes in-flight export of PostgreSql #2744 (#2745) --- docs/CHANGELOG-v1.md | 6 ++ docs/en/rules/Azure.PostgreSQL.AAD.md | 54 ++++++----- .../Pipeline/Export/ResourceExportVisitor.cs | 92 +++++++++++++++---- 3 files changed, 110 insertions(+), 42 deletions(-) diff --git a/docs/CHANGELOG-v1.md b/docs/CHANGELOG-v1.md index 7aea8c1de1a..6f48e7cf119 100644 --- a/docs/CHANGELOG-v1.md +++ b/docs/CHANGELOG-v1.md @@ -32,6 +32,12 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +What's changed since v1.34.1: + +- Bug fixes: + - Fixed export of in-flight data for flexible PostgreSQL servers by @BernieWhite. + [#2744](https://github.com/Azure/PSRule.Rules.Azure/issues/2744) + ## v1.34.1 What's changed since v1.34.0: diff --git a/docs/en/rules/Azure.PostgreSQL.AAD.md b/docs/en/rules/Azure.PostgreSQL.AAD.md index 03e45a8852c..1c8892334fe 100644 --- a/docs/en/rules/Azure.PostgreSQL.AAD.md +++ b/docs/en/rules/Azure.PostgreSQL.AAD.md @@ -1,22 +1,23 @@ --- +reviewed: 2024-03-09 severity: Critical pillar: Security -category: Authentication +category: SE:05 Identity and access management resource: Azure Database for PostgreSQL online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.PostgreSQL.AAD/ --- -# Use AAD authentication with PostgreSQL databases +# Use Entra ID authentication with PostgreSQL databases ## SYNOPSIS -Use Azure Active Directory (AAD) authentication with Azure Database for PostgreSQL databases. +Use Entra ID authentication with Azure Database for PostgreSQL databases. ## DESCRIPTION -Azure Database for PostgreSQL offer two authentication models, Azure Active Directory (AAD) and PostgreSQL logins. -AAD authentication supports centialized identity management in addition to modern password protections. -Some of the benefits of AAD authentication over PostgreSQL authentication including: +Azure Database for PostgreSQL offer two authentication models, Entra ID (previously knows as Azure AD) and PostgreSQL logins. +Entra ID authentication supports centralized identity management in addition to modern password protections. +Some of the benefits of Entra ID authentication over PostgreSQL authentication including: - Support for Azure Multi-Factor Authentication (MFA). - Conditional-based access with Conditional Access. @@ -25,7 +26,7 @@ It is also possible to disable PostgreSQL authentication entirely for the flexib ## RECOMMENDATION -Consider using Azure Active Directory (AAD) authentication with Azure Database for PostgreSQL databases. +Consider using Entra ID authentication with Azure Database for PostgreSQL databases. Additionally, consider disabling PostgreSQL authentication. ## EXAMPLES @@ -35,9 +36,9 @@ Additionally, consider disabling PostgreSQL authentication. To deploy Azure Database for PostgreSQL flexible servers that pass this rule: - Configure the `Microsoft.DBforPostgreSQL/flexibleServers/administrators` sub-resource. -- Set the `properties.principalName` to the user principal name of the AAD administrator user, group, or application. -- Set the `properties.principalType` to the principal type used to represent the type of AAD administrator. -- Set the `properties.tenantId` to the tenant ID of the AAD administrator user, group, or application. +- Set the `properties.principalName` to the user principal name of the Entra ID administrator user, group, or application. +- Set the `properties.principalType` to the principal type used to represent the type of Entra ID administrator. +- Set the `properties.tenantId` to the tenant ID of the Entra ID administrator user, group, or application. For example: @@ -61,9 +62,9 @@ To deploy Azure Database for PostgreSQL single servers that pass this rule: - Configure the `Microsoft.DBforPostgreSQL/servers/administrators` sub-resource. - Set the `properties.administratorType` to `ActiveDirectory`. -- Set the `properties.login` to the AAD administrator login object name. -- Set the `properties.sid` to the object ID GUID of the AAD administrator user, group, or application. -- Set the `properties.tenantId` to the tenant ID of the AAD administrator user, group, or application. +- Set the `properties.login` to the Entra ID administrator login object name. +- Set the `properties.sid` to the object ID GUID of the Entra ID administrator user, group, or application. +- Set the `properties.tenantId` to the tenant ID of the Entra ID administrator user, group, or application. For example: @@ -89,9 +90,9 @@ For example: To deploy Azure Database for PostgreSQL flexible servers that pass this rule: - Configure the `Microsoft.DBforPostgreSQL/flexibleServers/administrators` sub-resource. -- Set the `properties.principalName` to the user principal name of the AAD administrator user, group, or application. -- Set the `properties.principalType` to the principal type used to represent the type of AAD administrator. -- Set the `properties.tenantId` to the tenant ID of the AAD administrator user, group, or application. +- Set the `properties.principalName` to the user principal name of the Entra ID administrator user, group, or application. +- Set the `properties.principalType` to the principal type used to represent the type of Entra ID administrator. +- Set the `properties.tenantId` to the tenant ID of the Entra ID administrator user, group, or application. For example: @@ -111,9 +112,9 @@ To deploy Azure Database for PostgreSQL single servers that pass this rule: - Configure the `Microsoft.DBforPostgreSQL/servers/administrators` sub-resource. - Set the `properties.administratorType` to `ActiveDirectory`. -- Set the `properties.login` to the AAD administrator login object name. -- Set the `properties.sid` to the object ID GUID of the AAD administrator user, group, or application. -- Set the `properties.tenantId` to the tenant ID of the AAD administrator user, group, or application. +- Set the `properties.login` to the Entra ID administrator login object name. +- Set the `properties.sid` to the object ID GUID of the Entra ID administrator user, group, or application. +- Set the `properties.tenantId` to the tenant ID of the Entra ID administrator user, group, or application. For example: @@ -132,15 +133,18 @@ resource aadAdmin 'Microsoft.DBforPostgreSQL/servers/administrators@2017-12-01' ## NOTES -The single server deployment model is limited to only one Azure AD admin at a time and does not support enforcing AAD-authentication only. +The single server deployment model is limited to: + +- Only one Azure AD admin at a time. +- Does not support enforcing Entra ID authentication only. ## LINKS -- [Use modern password protection](https://learn.microsoft.com/azure/architecture/framework/security/design-identity-authentication#use-modern-password-protection) -- [Azure Active Directory Authentication with PostgreSQL Flexible Server](https://learn.microsoft.com/azure/postgresql/flexible-server/concepts-azure-ad-authentication#how-azure-ad-works-in-flexible-server) -- [Use Azure AD for authentication with Azure Database for PostgreSQL - Flexible Server](https://learn.microsoft.com/azure/postgresql/flexible-server/how-to-configure-sign-in-azure-ad-authentication) -- [Use Azure AD for authentication with Azure Database for PostgreSQL - Single Server](https://learn.microsoft.com/azure/postgresql/single-server/how-to-configure-sign-in-azure-ad-authentication) -- [Azure Active Directory Authentication (Single Server VS Flexible Server)](https://learn.microsoft.com/azure/postgresql/flexible-server/concepts-azure-ad-authentication#azure-active-directory-authentication-single-server-vs-flexible-server) +- [SE:05 Identity and access management](https://learn.microsoft.com/azure/well-architected/security/identity-access) +- [How Microsoft Entra ID Works in Azure Database for PostgreSQL flexible server](https://learn.microsoft.com/azure/postgresql/flexible-server/concepts-azure-ad-authentication#how-azure-ad-works-in-flexible-server) +- [Use Microsoft Entra ID for authentication with Azure Database for PostgreSQL - Flexible Server](https://learn.microsoft.com/azure/postgresql/flexible-server/how-to-configure-sign-in-azure-ad-authentication) +- [Use Microsoft Entra ID for authentication with PostgreSQL](https://learn.microsoft.com/azure/postgresql/single-server/how-to-configure-sign-in-azure-ad-authentication) +- [Microsoft Entra authentication (Azure Database for PostgreSQL single Server vs Azure Database for PostgreSQL flexible server)](https://learn.microsoft.com/azure/postgresql/flexible-server/concepts-azure-ad-authentication#azure-active-directory-authentication-single-server-vs-flexible-server) - [Azure security baseline for Azure Database for PostgreSQL - Flexible Server](https://learn.microsoft.com/security/benchmark/azure/baselines/azure-database-for-postgresql-flexible-server-security-baseline) - [Azure security baseline for Azure Database for PostgreSQL - Single Server](https://learn.microsoft.com/security/benchmark/azure/baselines/postgresql-security-baseline) - [IM-1: Use centralized identity and authentication system](https://learn.microsoft.com/security/benchmark/azure/baselines/azure-database-for-postgresql-flexible-server-security-baseline#im-1-use-centralized-identity-and-authentication-system) diff --git a/src/PSRule.Rules.Azure/Pipeline/Export/ResourceExportVisitor.cs b/src/PSRule.Rules.Azure/Pipeline/Export/ResourceExportVisitor.cs index 385b57cc623..943dbd52678 100644 --- a/src/PSRule.Rules.Azure/Pipeline/Export/ResourceExportVisitor.cs +++ b/src/PSRule.Rules.Azure/Pipeline/Export/ResourceExportVisitor.cs @@ -32,6 +32,17 @@ internal sealed class ResourceExportVisitor private const string PROPERTY_FIREWALLRULES = "firewallRules"; private const string PROPERTY_SECURITYALERTPOLICIES = "securityAlertPolicies"; private const string PROPERTY_CONFIGURATIONS = "configurations"; + private const string PROPERTY_ADMINISTRATORS = "administrators"; + private const string PROPERTY_VULNERABILITYASSESSMENTS = "vulnerabilityAssessments"; + private const string PROPERTY_AUDITINGSETTINGS = "auditingSettings"; + private const string PROPERTY_CUSTOMDOMAINS = "customDomains"; + private const string PROPERTY_WEBHOOKS = "webhooks"; + private const string PROPERTY_ORIGINGROUPS = "originGroups"; + private const string PROPERTY_REPLICATIONS = "replications"; + private const string PROPERTY_TASKS = "tasks"; + private const string PROPERTY_SECURITYPOLICIES = "securityPolicies"; + private const string PROPERTY_CONTAINERS = "containers"; + private const string PROPERTY_SHARES = "shares"; private const string TYPE_CONTAINERSERVICE_MANAGEDCLUSTERS = "Microsoft.ContainerService/managedClusters"; private const string TYPE_CONTAINERREGISTRY_REGISTRIES = "Microsoft.ContainerRegistry/registries"; @@ -44,7 +55,9 @@ internal sealed class ResourceExportVisitor private const string TYPE_SQL_SERVERS = "Microsoft.Sql/servers"; private const string TYPE_SQL_DATABASES = "Microsoft.Sql/servers/databases"; private const string TYPE_POSTGRESQL_SERVERS = "Microsoft.DBforPostgreSQL/servers"; + private const string TYPE_POSTGRESQL_FLEXABLESERVERS = "Microsoft.DBforPostgreSQL/flexibleServers"; private const string TYPE_MYSQL_SERVERS = "Microsoft.DBforMySQL/servers"; + private const string TYPE_MYSQL_FLEXABLESERVERS = "Microsoft.DBforMySQL/flexibleServers"; private const string TYPE_STORAGE_ACCOUNTS = "Microsoft.Storage/storageAccounts"; private const string TYPE_WEB_APP = "Microsoft.Web/sites"; private const string TYPE_WEB_APPSLOT = "Microsoft.Web/sites/slots"; @@ -83,10 +96,14 @@ internal sealed class ResourceExportVisitor private const string APIVERSION_2022_08_01 = "2022-08-01"; private const string APIVERSION_2022_11_20_PREVIEW = "2022-11-20-preview"; private const string APIVERSION_2022_04_01 = "2022-04-01"; + private const string APIVERSION_2022_09_01 = "2022-09-01"; private const string APIVERSION_2022_09_10 = "2022-09-10"; - private const string APIVERSION_2022_05_01 = "2022-05-01"; + private const string APIVERSION_2023_01_01 = "2023-01-01"; private const string APIVERSION_2023_04_01 = "2023-04-01"; private const string APIVERSION_2023_05_01 = "2023-05-01"; + private const string APIVERSION_2023_06_30 = "2023-06-30"; + private const string APIVERSION_2023_01_01_PREVIEW = "2023-01-01-preview"; + private const string APIVERSION_2023_03_01_PREVIEW = "2023-03-01-preview"; private readonly ProviderData _ProviderData; @@ -156,7 +173,9 @@ await VisitAKSCluster(resourceContext, resource, resourceType, resourceId) || await VisitSqlServers(resourceContext, resource, resourceType, resourceId) || await VisitSqlDatabase(resourceContext, resource, resourceType, resourceId) || await VisitPostgreSqlServer(resourceContext, resource, resourceType, resourceId) || + await VisitPostgreSqlFlexibleServer(resourceContext, resource, resourceType, resourceId) || await VisitMySqlServer(resourceContext, resource, resourceType, resourceId) || + await VisitMySqlFlexibleServer(resourceContext, resource, resourceType, resourceId) || await VisitStorageAccount(resourceContext, resource, resourceType, resourceId) || await VisitWebApp(resourceContext, resource, resourceType, resourceId) || await VisitRecoveryServicesVault(resourceContext, resource, resourceType, resourceId) || @@ -368,7 +387,7 @@ private static async Task VisitWebApp(ResourceContext context, JObject res !string.Equals(resourceType, TYPE_WEB_APPSLOT, StringComparison.OrdinalIgnoreCase)) return false; - AddSubResource(resource, await GetSubResourcesByType(context, resourceId, "config", "2022-03-01")); + AddSubResource(resource, await GetSubResourcesByType(context, resourceId, "config", APIVERSION_2022_09_01)); return true; } @@ -377,16 +396,31 @@ private static async Task VisitStorageAccount(ResourceContext context, JOb if (!string.Equals(resourceType, TYPE_STORAGE_ACCOUNTS, StringComparison.OrdinalIgnoreCase)) return false; + // Get blob services. if (resource.TryGetProperty(PROPERTY_KIND, out var kind) && !string.Equals(kind, "FileStorage", StringComparison.OrdinalIgnoreCase)) { - var blobServices = await GetSubResourcesByType(context, resourceId, "blobServices", APIVERSION_2022_05_01); + var blobServices = await GetSubResourcesByType(context, resourceId, "blobServices", APIVERSION_2023_01_01); AddSubResource(resource, blobServices); foreach (var blobService in blobServices) { - AddSubResource(resource, await GetSubResourcesByType(context, blobService[PROPERTY_ID].Value(), "containers", APIVERSION_2022_05_01)); + AddSubResource(resource, await GetSubResourcesByType(context, blobService[PROPERTY_ID].Value(), PROPERTY_CONTAINERS, APIVERSION_2023_01_01)); } } + + // Get file services. + else if (kind != null && + !string.Equals(kind, "BlobStorage", StringComparison.OrdinalIgnoreCase) && + !string.Equals(kind, "BlockBlobStorage", StringComparison.OrdinalIgnoreCase)) + { + var blobServices = await GetSubResourcesByType(context, resourceId, "fileServices", APIVERSION_2023_01_01); + AddSubResource(resource, blobServices); + foreach (var blobService in blobServices) + { + AddSubResource(resource, await GetSubResourcesByType(context, blobService[PROPERTY_ID].Value(), PROPERTY_SHARES, APIVERSION_2023_01_01)); + } + } + AddSubResource(resource, await GetSubResourcesByProvider(context, resourceId, PROVIDERTYPE_DEFENDERFORSTORAGESETTINGS, "2022-12-01-preview", ignoreNotFound: true)); return true; } @@ -396,23 +430,47 @@ private static async Task VisitMySqlServer(ResourceContext context, JObjec if (!string.Equals(resourceType, TYPE_MYSQL_SERVERS, StringComparison.OrdinalIgnoreCase)) return false; + AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_ADMINISTRATORS, APIVERSION_2017_12_01)); AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_FIREWALLRULES, APIVERSION_2017_12_01)); AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_SECURITYALERTPOLICIES, APIVERSION_2017_12_01)); AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_CONFIGURATIONS, APIVERSION_2017_12_01)); return true; } + private static async Task VisitMySqlFlexibleServer(ResourceContext context, JObject resource, string resourceType, string resourceId) + { + if (!string.Equals(resourceType, TYPE_MYSQL_FLEXABLESERVERS, StringComparison.OrdinalIgnoreCase)) + return false; + + AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_ADMINISTRATORS, APIVERSION_2023_06_30)); + AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_FIREWALLRULES, APIVERSION_2023_06_30)); + AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_CONFIGURATIONS, APIVERSION_2023_06_30)); + return true; + } + private static async Task VisitPostgreSqlServer(ResourceContext context, JObject resource, string resourceType, string resourceId) { if (!string.Equals(resourceType, TYPE_POSTGRESQL_SERVERS, StringComparison.OrdinalIgnoreCase)) return false; + AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_ADMINISTRATORS, APIVERSION_2017_12_01)); AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_FIREWALLRULES, APIVERSION_2017_12_01)); AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_SECURITYALERTPOLICIES, APIVERSION_2017_12_01)); AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_CONFIGURATIONS, APIVERSION_2017_12_01)); return true; } + private static async Task VisitPostgreSqlFlexibleServer(ResourceContext context, JObject resource, string resourceType, string resourceId) + { + if (!string.Equals(resourceType, TYPE_POSTGRESQL_FLEXABLESERVERS, StringComparison.OrdinalIgnoreCase)) + return false; + + AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_ADMINISTRATORS, APIVERSION_2023_03_01_PREVIEW)); + AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_FIREWALLRULES, APIVERSION_2023_03_01_PREVIEW)); + AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_CONFIGURATIONS, APIVERSION_2023_03_01_PREVIEW)); + return true; + } + private static async Task VisitSqlDatabase(ResourceContext context, JObject resource, string resourceType, string resourceId) { if (!string.Equals(resourceType, TYPE_SQL_DATABASES, StringComparison.OrdinalIgnoreCase)) @@ -432,10 +490,10 @@ private static async Task VisitSqlServers(ResourceContext context, JObject return false; AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_FIREWALLRULES, APIVERSION_2021_11_01)); - AddSubResource(resource, await GetSubResourcesByType(context, resourceId, "administrators", APIVERSION_2021_11_01)); + AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_ADMINISTRATORS, APIVERSION_2021_11_01)); AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_SECURITYALERTPOLICIES, APIVERSION_2021_11_01)); - AddSubResource(resource, await GetSubResourcesByType(context, resourceId, "vulnerabilityAssessments", APIVERSION_2021_11_01)); - AddSubResource(resource, await GetSubResourcesByType(context, resourceId, "auditingSettings", APIVERSION_2021_11_01)); + AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_VULNERABILITYASSESSMENTS, APIVERSION_2021_11_01)); + AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_AUDITINGSETTINGS, APIVERSION_2021_11_01)); return true; } @@ -472,12 +530,12 @@ private static async Task VisitContainerRegistry(ResourceContext context, if (!string.Equals(resourceType, TYPE_CONTAINERREGISTRY_REGISTRIES, StringComparison.OrdinalIgnoreCase)) return false; - AddSubResource(resource, await GetSubResourcesByType(context, resourceId, "replications", "2023-01-01-preview")); - AddSubResource(resource, await GetSubResourcesByType(context, resourceId, "webhooks", "2023-01-01-preview")); - AddSubResource(resource, await GetSubResourcesByType(context, resourceId, "tasks", "2019-04-01")); + AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_REPLICATIONS, APIVERSION_2023_01_01_PREVIEW)); + AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_WEBHOOKS, APIVERSION_2023_01_01_PREVIEW)); + AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_TASKS, "2019-04-01")); // Handle usage information that does not include a strong type. - foreach (var usage in await GetSubResourcesByType(context, resourceId, "listUsages", "2023-01-01-preview")) + foreach (var usage in await GetSubResourcesByType(context, resourceId, "listUsages", APIVERSION_2023_01_01_PREVIEW)) { usage[PROPERTY_TYPE] = TYPE_CONTAINERREGISTRY_REGISTRIES_LISTUSAGES; AddSubResource(resource, usage); @@ -490,8 +548,8 @@ private static async Task VisitCDNEndpoint(ResourceContext context, JObjec if (!string.Equals(resourceType, TYPE_CDN_PROFILES_ENDPOINTS, StringComparison.OrdinalIgnoreCase)) return false; - AddSubResource(resource, await GetSubResourcesByType(context, resourceId, "customDomains", APIVERSION_2023_05_01)); - AddSubResource(resource, await GetSubResourcesByType(context, resourceId, "originGroups", APIVERSION_2023_05_01)); + AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_CUSTOMDOMAINS, APIVERSION_2023_05_01)); + AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_ORIGINGROUPS, APIVERSION_2023_05_01)); await GetDiagnosticSettings(context, resource, resourceId); return true; @@ -502,11 +560,11 @@ private static async Task VisitCDNProfile(ResourceContext context, JObject if (!string.Equals(resourceType, TYPE_CDN_PROFILES, StringComparison.OrdinalIgnoreCase)) return false; - AddSubResource(resource, await GetSubResourcesByType(context, resourceId, "customDomains", APIVERSION_2023_05_01)); - AddSubResource(resource, await GetSubResourcesByType(context, resourceId, "originGroups", APIVERSION_2023_05_01)); + AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_CUSTOMDOMAINS, APIVERSION_2023_05_01)); + AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_ORIGINGROUPS, APIVERSION_2023_05_01)); AddSubResource(resource, await GetSubResourcesByType(context, resourceId, "ruleSets", APIVERSION_2023_05_01)); AddSubResource(resource, await GetSubResourcesByType(context, resourceId, "secrets", APIVERSION_2023_05_01)); - AddSubResource(resource, await GetSubResourcesByType(context, resourceId, "securityPolicies", APIVERSION_2023_05_01)); + AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_SECURITYPOLICIES, APIVERSION_2023_05_01)); return true; } @@ -516,7 +574,7 @@ private static async Task VisitAutomationAccount(ResourceContext context, return false; AddSubResource(resource, await GetSubResourcesByType(context, resourceId, "variables", "2022-08-08")); - AddSubResource(resource, await GetSubResourcesByType(context, resourceId, "webhooks", "2015-10-31")); + AddSubResource(resource, await GetSubResourcesByType(context, resourceId, PROPERTY_WEBHOOKS, "2015-10-31")); return true; }