Skip to content

Queryable Encryption tutorial in PHP #6293

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions source/includes/qe-tutorials/php/.env.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# MongoDB connection uri and automatic encryption shared library path

MONGODB_URI="<Your MongoDB URI>"
KMS_PROVIDER="local" # or "aws", "azure", "gcp", "kmip"

# AWS Credentials

AWS_ACCESS_KEY_ID="<Your AWS access key ID>"
AWS_SECRET_ACCESS_KEY="<Your AWS secret access key>"
AWS_KEY_REGION="<Your AWS key region>"
AWS_KEY_ARN="<Your AWS key ARN>"

# Azure Credentials

AZURE_TENANT_ID="<Your Azure tenant ID>"
AZURE_CLIENT_ID="<Your Azure client ID>"
AZURE_CLIENT_SECRET="<Your Azure client secret>"
AZURE_KEY_NAME="<Your Azure key name>"
AZURE_KEY_VERSION="<Your Azure key version>"
AZURE_KEY_VAULT_ENDPOINT="<Your Azure key vault endpoint>"

# GCP Credentials

GCP_EMAIL="<Your GCP email>"
GCP_PRIVATE_KEY="<Your GCP private key>"

GCP_PROJECT_ID="<Your GCP project ID>"
GCP_LOCATION="<Your GCP location>"
GCP_KEY_RING="<Your GCP key ring>"
GCP_KEY_NAME="<Your GCP key name>"
GCP_KEY_VERSION="<Your GCP key version>"

# KMIP Credentials

KMIP_KMS_ENDPOINT="<Endpoint for your KMIP KMS>"
KMIP_TLS_CA_FILE="<Full path to your KMIP certificate authority file>"
KMIP_TLS_CERT_FILE="<Full path to your client certificate file>"
4 changes: 4 additions & 0 deletions source/includes/qe-tutorials/php/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.env
composer.lock
customer-master-key.txt
vendor
10 changes: 10 additions & 0 deletions source/includes/qe-tutorials/php/composer.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
195 changes: 195 additions & 0 deletions source/includes/qe-tutorials/php/queryable-encryption-helpers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
<?php

namespace MongoDB\Tutorials\QueryableEncryption;

use InvalidArgumentException;
use MongoDB\Client;
use MongoDB\Driver\ClientEncryption;
use MongoDB\Driver\Exception\Exception;
use RuntimeException;

function dropExistingCollection(Client $client, string $databaseName): void
{
$database = $client->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);
}
}
112 changes: 112 additions & 0 deletions source/includes/qe-tutorials/php/queryable-encryption-tutorial.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?php

namespace MongoDB\Tutorials\QueryableEncryption;

use MongoDB\Exception\RuntimeException;
use Symfony\Component\Dotenv\Dotenv;

require __DIR__.'/vendor/autoload.php';
require __DIR__.'/queryable-encryption-helpers.php';

(new Dotenv())->usePutenv()->loadEnv(__DIR__.'/.env');

// start-setup-application-variables
// KMS provider name should be one of the following: "aws", "gcp", "azure", "kmip" or "local"
$kmsProviderName = '<KMS provider name>';

$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,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a difference with the node tutorial: keyId must be defined but it can be null. In node, it can be omitted.

fields: [
{
path: "patientRecord.ssn",
bsonType: "string",
queries: { queryType: "equality" },
},
{
path: "patientRecord.billing",
bsonType: "object",
},
],
},

],
[
'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
Loading