diff --git a/source/includes/qe-tutorials/php/.env.dist b/source/includes/qe-tutorials/php/.env.dist new file mode 100644 index 00000000000..20e644b3989 --- /dev/null +++ b/source/includes/qe-tutorials/php/.env.dist @@ -0,0 +1,37 @@ +# MongoDB connection uri and automatic encryption shared library path + +MONGODB_URI="" +KMS_PROVIDER="local" # or "aws", "azure", "gcp", "kmip" + +# AWS Credentials + +AWS_ACCESS_KEY_ID="" +AWS_SECRET_ACCESS_KEY="" +AWS_KEY_REGION="" +AWS_KEY_ARN="" + +# Azure Credentials + +AZURE_TENANT_ID="" +AZURE_CLIENT_ID="" +AZURE_CLIENT_SECRET="" +AZURE_KEY_NAME="" +AZURE_KEY_VERSION="" +AZURE_KEY_VAULT_ENDPOINT="" + +# GCP Credentials + +GCP_EMAIL="" +GCP_PRIVATE_KEY="" + +GCP_PROJECT_ID="" +GCP_LOCATION="" +GCP_KEY_RING="" +GCP_KEY_NAME="" +GCP_KEY_VERSION="" + +# KMIP Credentials + +KMIP_KMS_ENDPOINT="" +KMIP_TLS_CA_FILE="" +KMIP_TLS_CERT_FILE="" diff --git a/source/includes/qe-tutorials/php/.gitignore b/source/includes/qe-tutorials/php/.gitignore new file mode 100644 index 00000000000..0aac29d9818 --- /dev/null +++ b/source/includes/qe-tutorials/php/.gitignore @@ -0,0 +1,4 @@ +.env +composer.lock +customer-master-key.txt +vendor diff --git a/source/includes/qe-tutorials/php/composer.json b/source/includes/qe-tutorials/php/composer.json new file mode 100644 index 00000000000..1c215937d59 --- /dev/null +++ b/source/includes/qe-tutorials/php/composer.json @@ -0,0 +1,10 @@ +{ + "name": "mongodb/qe-tutorial", + "type": "project", + "require": { + "php": ">=8.1", + "ext-mongodb": "^1.21|^2", + "mongodb/mongodb": "^1.21|^2", + "symfony/dotenv": "^6.4|^7" + } +} diff --git a/source/includes/qe-tutorials/php/queryable-encryption-helpers.php b/source/includes/qe-tutorials/php/queryable-encryption-helpers.php new file mode 100644 index 00000000000..4b36c8a04b4 --- /dev/null +++ b/source/includes/qe-tutorials/php/queryable-encryption-helpers.php @@ -0,0 +1,195 @@ +getDatabase($databaseName); + $database->drop(); +} + +function getKMSProviderCredentials(string $kmsProviderName): array +{ + switch ($kmsProviderName) { + case 'aws': + // start-aws-kms-credentials + $kmsProviders = [ + 'aws' => [ + 'accessKeyId' => getenv('AWS_ACCESS_KEY_ID'), // Your AWS access key ID + 'secretAccessKey' => getenv('AWS_SECRET_ACCESS_KEY'), // Your AWS secret access key + ], + ]; + // end-aws-kms-credentials + return $kmsProviders; + case 'azure': + // start-azure-kms-credentials + $kmsProviders = [ + 'azure' => [ + 'tenantId' => getenv('AZURE_TENANT_ID'), // Your Azure tenant ID + 'clientId' => getenv('AZURE_CLIENT_ID'), // Your Azure client ID + 'clientSecret' => getenv('AZURE_CLIENT_SECRET'), // Your Azure client secret + ], + ]; + // end-azure-kms-credentials + return $kmsProviders; + case 'gcp': + // start-gcp-kms-credentials + $kmsProviders = [ + 'gcp' => [ + 'email' => getenv('GCP_EMAIL'), // Your GCP email + 'privateKey' => getenv('GCP_PRIVATE_KEY'), // Your GCP private key + ], + ]; + // end-gcp-kms-credentials + return $kmsProviders; + case 'kmip': + // start-kmip-kms-credentials + $kmsProviders = [ + 'kmip' => [ + 'endpoint' => getenv('KMIP_ENDPOINT'), // Your KMIP endpoint + ], + ]; + // end-kmip-kms-credentials + return $kmsProviders; + case 'local': + // start-generate-local-key + if (!file_exists('./customer-master-key.txt')) { + file_put_contents('./customer-master-key.txt', base64_encode(random_bytes(96))); + } + // end-generate-local-key + + // start-get-local-key + $localMasterKey = file_get_contents('./customer-master-key.txt'); + $kmsProviders = [ + 'local' => [ + 'key' => $localMasterKey, + ], + ]; + // end-get-local-key + return $kmsProviders; + default: + throw new InvalidArgumentException(sprintf('Unrecognized value for KMS provider name. Must be one of: aws, gcp, azure, kmip, or local. Got "%s".', $kmsProviderName)); + } +} + +function getCustomerMasterKeyCredentials(string $kmsProviderName): array +{ + switch ($kmsProviderName) { + case 'aws': + // start-aws-cmk-credentials + $customerMasterKeyCredentials = [ + 'key' => getenv('AWS_KEY_ARN'), // Your AWS key ID + 'region' => getenv('AWS_REGION'), // Your AWS region + ]; + // end-aws-cmk-credentials + return $customerMasterKeyCredentials; + case "azure": + // start-azure-cmk-credentials + $customerMasterKeyCredentials = [ + 'keyVaultEndpoint' => getenv('AZURE_KEY_VAULT_ENDPOINT'), // Your Azure Key Vault Endpoint + 'keyName' => getenv('AZURE_KEY_NAME'), // Your Azure Key Name + ]; + // end-azure-cmk-credentials + return $customerMasterKeyCredentials; + case "gcp": + // start-gcp-cmk-credentials + $customerMasterKeyCredentials = [ + 'projectId' => getenv('GCP_PROJECT_ID'), // Your GCP Project ID + 'location' => getenv('GCP_LOCATION'), // Your GCP Key Location + 'keyRing' => getenv('GCP_KEY_RING'), // Your GCP Key Ring + 'keyName' => getenv('GCP_KEY_NAME'), // Your GCP Key Name + ]; + // end-gcp-cmk-credentials + return $customerMasterKeyCredentials; + case "kmip": + case "local": + // start-kmip-local-cmk-credentials + $customerMasterKeyCredentials = []; + // end-kmip-local-cmk-credentials + return $customerMasterKeyCredentials; + default: + throw new InvalidArgumentException(sprintf('Unrecognized value for KMS provider name. Must be one of: aws, gcp, azure, kmip, or local. Got "%s".', $kmsProviderName)); + } +} + +function getAutoEncryptionOptions( + string $kmsProviderName, + string $keyVaultNamespace, + array $kmsProviders, +): array +{ + if ($kmsProviderName === 'kmip') { + $tlsOptions = getKmipTlsOptions(); + + // start-kmip-encryption-options + $autoEncryptionOptions = [ + 'keyVaultNamespace' => $keyVaultNamespace, + 'kmsProviders' => $kmsProviders, + 'tlsOptions' => $tlsOptions, + ]; + // end-kmip-encryption-options + return $autoEncryptionOptions; + } else { + // start-auto-encryption-options + $autoEncryptionOptions = [ + 'keyVaultNamespace' => $keyVaultNamespace, + 'kmsProviders' => $kmsProviders, + ]; + // end-auto-encryption-options + + return $autoEncryptionOptions; + } +} + +function getKmipTlsOptions(): array +{ + // start-tls-options + $tlsOptions = [ + 'kmip' => [ + 'tlsCAFile' => getenv('KMIP_TLS_CA_FILE'), // Path to your TLS CA file + 'tlsCertificateKeyFile' => getenv('KMIP_TLS_CERT_FILE'), // Path to your TLS certificate key file + ], + ]; + // end-tls-options + return $tlsOptions; +} + +function getClientEncryption(Client $encryptedClient, array $autoEncryptionOptions): ClientEncryption +{ + // start-create-client-encryption + $clientEncryption = $encryptedClient->createClientEncryption($autoEncryptionOptions); + // end-create-client-encryption + + return $clientEncryption; +} + +function createEncryptedCollection( + Client $client, + ClientEncryption $clientEncryption, + string $encryptedDatabase, + string $encryptedCollectionName, + string $kmsProviderName, + array $encryptedFieldsMap, + ?array $customerMasterKeyCredentials, +): void +{ + try { + // start-create-encrypted-collection + $client->getDatabase($encryptedDatabase)->createEncryptedCollection( + $encryptedCollectionName, + $clientEncryption, + $kmsProviderName, + $customerMasterKeyCredentials, + $encryptedFieldsMap, + ); + // end-create-encrypted-collection + } catch (Exception $e) { + throw new RuntimeException(sprintf('Unable to create encrypted collection: %s', $e->getMessage()), 0, $e); + } +} diff --git a/source/includes/qe-tutorials/php/queryable-encryption-tutorial.php b/source/includes/qe-tutorials/php/queryable-encryption-tutorial.php new file mode 100644 index 00000000000..cfd4cc428a5 --- /dev/null +++ b/source/includes/qe-tutorials/php/queryable-encryption-tutorial.php @@ -0,0 +1,112 @@ +usePutenv()->loadEnv(__DIR__.'/.env'); + +// start-setup-application-variables +// KMS provider name should be one of the following: "aws", "gcp", "azure", "kmip" or "local" +$kmsProviderName = ''; + +$uri = getenv('MONGODB_URI'); // Your connection URI + +$keyVaultDatabaseName = 'encryption'; +$keyVaultCollectionName = '__keyVault'; +$keyVaultNamespace = $keyVaultDatabaseName . '.' . $keyVaultCollectionName; +$encryptedDatabaseName = 'medicalRecords'; +$encryptedCollectionName = 'patients'; +// end-setup-application-variables + +// Override the KMS provider name with the environment variable if set +$kmsProviderName = getenv('KMS_PROVIDER') ?: throw new RuntimeException('Variable KMS_PROVIDER not set.'); + +$kmsProviderCredentials = getKMSProviderCredentials($kmsProviderName); +$customerMasterKeyCredentials = getCustomerMasterKeyCredentials($kmsProviderName); + +$autoEncryptionOptions = getAutoEncryptionOptions( + $kmsProviderName, + $keyVaultNamespace, + $kmsProviderCredentials +); + +// start-create-client +$encryptedClient = new \MongoDB\Client($uri, [], [ + 'autoEncryption' => $autoEncryptionOptions, +]); +// end-create-client + +dropExistingCollection($encryptedClient, $encryptedDatabaseName); +dropExistingCollection($encryptedClient, $keyVaultDatabaseName); + +// start-encrypted-fields-map +$encryptedFieldsMap = [ + 'encryptedFields' => [ + 'fields' => [ + [ + 'path' => 'patientRecord.ssn', + 'bsonType' => 'string', + 'queries' => ['queryType' => 'equality'], + 'keyId' => null, + ], + [ + 'path' => 'patientRecord.billing', + 'bsonType' => 'object', + 'keyId' => null, + ], + ], + ], +]; +// end-encrypted-fields-map + +$clientEncryption = getClientEncryption( + $encryptedClient, + $autoEncryptionOptions +); + +createEncryptedCollection( + $encryptedClient, + $clientEncryption, + $encryptedDatabaseName, + $encryptedCollectionName, + $kmsProviderName, + $encryptedFieldsMap, + $customerMasterKeyCredentials +); + +// start-insert-document +$patientDocument = [ + 'patientName' => 'Jon Doe', + 'patientId' => 12345678, + 'patientRecord' => [ + 'ssn' => '987-65-4320', + 'billing' => [ + 'type' => 'Visa', + 'number' => '4111111111111111', + ], + ], +]; + +$encryptedCollection = $encryptedClient + ->getDatabase($encryptedDatabaseName) + ->getCollection($encryptedCollectionName); + +$result = $encryptedCollection->insertOne($patientDocument); +// end-insert-document + +if ($result->isAcknowledged()) { + echo "Successfully inserted the patient document.\n"; +} + +// start-find-document +$findResult = $encryptedCollection->findOne([ + 'patientRecord.ssn' => '987-65-4320', +]); + +print_r($findResult); +// end-find-document diff --git a/source/includes/qe-tutorials/php/range-query.php b/source/includes/qe-tutorials/php/range-query.php new file mode 100644 index 00000000000..332f3ca72af --- /dev/null +++ b/source/includes/qe-tutorials/php/range-query.php @@ -0,0 +1,40 @@ + [ + 'fields' => [ + [ + 'path' => 'patientRecord.ssn', + 'bsonType' => 'string', + 'queries' => ['queryType' => 'equality'], + 'keyId' => null, + ], + [ + 'path' => 'patientRecord.billing', + 'bsonType' => 'object', + 'keyId' => null, + ], + [ + 'path' => 'patientRecord.billAmount', + 'bsonType' => 'int', + 'queries' => [ + 'queryType' => 'range', + 'sparsity' => 1, + 'trimFactor' => 4, + 'min' => 100, + 'max' => 2000, + ], + 'keyId' => null, + ], + ], + ], +]; +// end-enable-range + +// start-query-range +$findResult = $encryptedCollection->findOne([ + 'patientRecord.billAmount' => ['$gt' => 1000, '$lt' => 2000], +]); +print_r($findResult); +// end-query-range