π·πΊ Π ΡΡΡΠΊΠ°Ρ Π²Π΅ΡΡΠΈΡ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΠΈ
PHP/Laravel client library for Yandex Lockbox β a secure secrets storage service in Yandex Cloud.
Note: This package uses yandex-cloud-client-php for Yandex Cloud infrastructure management (authentication, organizations, clouds, folders).
- Yandex Lockbox Docs
- Quickstart Guide
- API Reference
- OAuth Token Guide
- API Endpoint:
https://lockbox.api.cloud.yandex.net/lockbox/v1
- β Full Yandex Lockbox API support
- β Automatic IAM token generation from OAuth token
- β Cloud infrastructure management via yandex-cloud-client-php
- β Async operation handling (wait for operations to complete)
- β Folder permissions management (list/assign access bindings)
- β PHP 8.0+ with strict types
- β Laravel 8-12 integration (service provider, facade, config)
- β Extensible token provider interface
- β Typed exceptions for better error handling
- β PSR-3 logger support
- β Comprehensive test coverage
composer require tigusigalpa/yandex-lockbox-phpFor mono-repo development, add to your root composer.json:
{
"repositories": [
{
"type": "path",
"url": "public_html/packages/yandex-lockbox-php"
}
],
"require": {
"tigusigalpa/yandex-lockbox-php": "*"
}
}Then run:
composer update tigusigalpa/yandex-lockbox-phpPublish the configuration file:
php artisan vendor:publish --tag=yandex-lockbox-configAdd environment variables to your .env:
# RECOMMENDED: Use OAuth token (starts with y0_, y1_, y2_, y3_)
# OAuth tokens don't expire and are automatically converted to IAM tokens
YANDEX_LOCKBOX_TOKEN=y0_your-oauth-token
# ALTERNATIVE: Use IAM token (starts with t1.)
# IAM tokens expire after 12 hours
# YANDEX_LOCKBOX_TOKEN=t1.your-iam-token
YANDEX_LOCKBOX_BASE_URI=https://lockbox.api.cloud.yandex.net/lockbox/v1
YANDEX_LOCKBOX_FOLDER_ID=your-default-folder-idDocumentation: OAuth Token Guide
Get token via OAuth request:
https://oauth.yandex.com/authorize?response_type=token&client_id=1a6990aa636648e9b2ef855fa7bec2fb
- Open the URL above in your browser
- Authorize the application
- Copy the OAuth token from the response URL (format:
y0_...,y1_...,y2_...,y3_...) - Add token to
.env(Laravel):YANDEX_LOCKBOX_TOKEN=y0_your-oauth-token
Or pass directly to OAuthTokenProvider:
use Tigusigalpa\YandexLockbox\Token\OAuthTokenProvider;
$tokenProvider = new OAuthTokenProvider('y0_your-oauth-token');Documentation: How to get IAM token
IAM token is generated automatically from OAuth token. But you can get it manually:
$tokenProvider = new OAuthTokenProvider('y0_your-oauth-token');
// Get IAM token (cached for 12 hours)
$iamToken = $tokenProvider->getToken();Alternative - using Yandex CLI:
yc iam create-tokenDocumentation: Retrieves the list of Cloud resources
List all clouds:
$tokenProvider = new OAuthTokenProvider('y0_your-oauth-token');
// Get cloud client for infrastructure management
$cloudClient = $tokenProvider->getCloudClient();
// Get all clouds
$clouds = $cloudClient->clouds()->list();
foreach ($clouds['clouds'] as $cloud) {
echo "Cloud: {$cloud['name']} (ID: {$cloud['id']})\n";
}
// Use first cloud
$cloudId = $clouds['clouds'][0]['id'];Or get first cloud directly:
// Get first cloud ID (convenience method)
$cloudId = $tokenProvider->getFirstCloudId();Documentation: Retrieves the list of Folder resources in the specified cloud
List all folders in cloud:
// Get all folders in cloud
$cloudClient = $tokenProvider->getCloudClient();
$folders = $cloudClient->folders()->list($cloudId);
foreach ($folders['folders'] as $folder) {
echo "Folder: {$folder['name']} (ID: {$folder['id']})\n";
}
// Use first folder
$folderId = $folders['folders'][0]['id'];Or get first folder directly:
// Get first folder ID (convenience method)
$folderId = $tokenProvider->getFirstFolderId($cloudId);
// Or get first folder from first cloud in one call
$folderId = $tokenProvider->getFirstFolderIdFromFirstCloud();Documentation: Access management in Yandex Lockbox
You need to get Subject ID (user account ID that you want to assign permissions to) first
**Documentation: ** Subjects that roles are assigned to
**Documentation: ** Retrieves the list of Yandex Passport user accounts
$subjectId = $manager->getUserIdByLogin('your-yandex-login'); // your-yandex-login@yandex.ruDocumentation: lockbox.editor
**Documentation: ** Setting up folder access permissions
$manager->assignRoleToFolder(
$iamToken,
$folderId,
$subjectId,
'lockbox.editor',
'userAccount',
true // waitForCompletion - waits until operation is done
);Documentation: Lockbox API, REST: Secret
Now you can use the folder ID to work with secrets:
use Tigusigalpa\YandexLockbox\Client;
use Tigusigalpa\YandexLockbox\Token\OAuthTokenProvider;
// Create client with OAuth token
$tokenProvider = new OAuthTokenProvider('y0_your-oauth-token');
$client = new Client($tokenProvider);
// List all secrets in a folder
$secrets = $client->listSecrets($folderId);
foreach ($secrets['secrets'] as $secret) {
echo "{$secret['name']} (ID: {$secret['id']})\n";
echo "Description: {$secret['description']}\n";
echo "Labels: " . json_encode($secret['labels']) . "\n";
echo "Status: {$secret['status']}\n";
echo "Created at: {$secret['createdAt']}\n";
echo "Updated at: {$secret['updatedAt']}\n";
echo "Current version: {$secret['currentVersion']}\n";
}
// Get secret metadata
// @see https://yandex.cloud/en/docs/lockbox/api-ref/Secret/get
$secret = $client->getSecret('your-secret-id');
// Get secret payload (actual values)
$payload = $client->getPayload('your-secret-id');
foreach ($payload['entries'] as $entry) {
echo "{$entry['key']}: {$entry['textValue']}\n"; // or {$entry['binaryValue']}
}
echo $payload['versionId'];
// Optional: get specific version
$payload = $client->getPayload('your-secret-id', 'version-id');
// Create a new secret
// @see https://yandex.cloud/en/docs/lockbox/api-ref/Secret/create
$created = $client->createSecret([
'folderId' => $folderId,
'name' => 'my-api-keys',
'description' => 'Production API keys',
'labels' => ['env' => 'production'],
]);
$secretId = $created['id'];
// Add a new version with secret values
// Uses POST /secrets/{id}:addVersion endpoint
// @see https://yandex.cloud/en/docs/lockbox/api-ref/Secret/addVersion
$version = $client->addVersion($secretId, [
'description' => 'Version with API keys', // Optional
'payloadEntries' => [
['key' => 'API_KEY', 'textValue' => 'super-secret-key'],
['key' => 'API_SECRET', 'textValue' => 'super-secret-value'],
],
]);
// Update secret metadata
$updated = $client->updateSecret($secretId, [
'name' => 'updated-name',
'description' => 'Updated description',
]);
// List all versions
$versions = $client->listVersions($secretId);
// Activate/Deactivate secret
$client->activateSecret($secretId);
$client->deactivateSecret($secretId);
// Schedule version destruction (7 days by default)
$client->scheduleVersionDestruction($secretId, 'version-id', '604800s');
// Cancel scheduled destruction
$client->cancelVersionDestruction($secretId, 'version-id');
// Delete secret
$client->deleteSecret($secretId);
// List operations
$operations = $client->listOperations($secretId);
// Access control
$bindings = $client->listAccessBindings($secretId);
$client->setAccessBindings($secretId, [
['roleId' => 'viewer', 'subject' => ['type' => 'userAccount', 'id' => 'user-id']],
]);Some Yandex Cloud operations (like assignRoleToFolder) are asynchronous and return an operation object with
done=false. You have two options:
Option 1: Wait for completion automatically
$manager = new OAuthTokenManager('y0_your-oauth-token');
$iamToken = $manager->getIamToken();
// Set waitForCompletion to true (6th parameter)
$result = $manager->assignRoleToFolder(
$iamToken,
'folder-id',
'user-id',
'lockbox.editor',
'userAccount',
true, // waitForCompletion
60 // maxWaitSeconds (optional, default: 60)
);
// $result['done'] will be trueOption 2: Poll operation status manually
// Start operation
$operation = $manager->assignRoleToFolder($iamToken, 'folder-id', 'user-id', 'lockbox.editor');
// Check if done
if (!$operation['done']) {
// Wait for operation to complete
$completed = $manager->waitForOperation(
$iamToken,
$operation['id'],
60 // maxWaitSeconds (optional)
);
if ($completed['done']) {
echo "Operation completed successfully!\n";
}
}
// Or check status without waiting
$status = $manager->getOperation($iamToken, $operation['id']);
echo "Operation status: " . ($status['done'] ? 'completed' : 'in progress') . "\n";List and manage access bindings (permissions) for folders:
use Tigusigalpa\YandexLockbox\Auth\OAuthTokenManager;
$manager = new OAuthTokenManager('y0_your-oauth-token');
$iamToken = $manager->getIamToken();
// List access bindings with pagination
$result = $manager->listFolderAccessBindings($iamToken, 'folder-id', 100);
foreach ($result['accessBindings'] as $binding) {
echo "Role: {$binding['roleId']}\n";
echo "Subject: {$binding['subject']['id']} ({$binding['subject']['type']})\n";
}
// Handle pagination if needed
if (isset($result['nextPageToken'])) {
$nextPage = $manager->listFolderAccessBindings(
$iamToken,
'folder-id',
100,
$result['nextPageToken']
);
}
// Get all bindings at once (automatic pagination)
$allBindings = $manager->getAllFolderAccessBindings($iamToken, 'folder-id');
echo "Total permissions: " . count($allBindings) . "\n";
// Group by role
$byRole = [];
foreach ($allBindings as $binding) {
$byRole[$binding['roleId']][] = $binding['subject'];
}Response structure:
[
'accessBindings' => [
[
'roleId' => 'lockbox.editor', // Role identifier
'subject' => [
'id' => 'ajef55nu903fiklhapf9', // User/SA ID
'type' => 'userAccount' // 'userAccount' or 'serviceAccount'
]
],
// ... more bindings
],
'nextPageToken' => 'token...' // Present if more pages available
]use Tigusigalpa\YandexLockbox\Laravel\Facades\Lockbox;
// List secrets using default folder from config
$secrets = Lockbox::listSecrets(config('lockbox.default_folder_id'));
// Get secret metadata
$secret = Lockbox::getSecret('secret-id');
// Get actual secret values
$payload = Lockbox::getPayload('secret-id');
foreach ($payload['entries'] as $entry) {
echo $entry['key'] . ': ' . $entry['textValue'] . PHP_EOL;
}
// Create secret
$created = Lockbox::createSecret([
'folderId' => config('lockbox.default_folder_id'),
'name' => 'laravel-secrets',
'description' => 'Laravel application secrets',
]);
// Add version
$version = Lockbox::addVersion('secret-id', [
'payloadEntries' => [
['key' => 'DB_PASSWORD', 'textValue' => env('DB_PASSWORD')],
['key' => 'APP_KEY', 'textValue' => env('APP_KEY')],
],
]);# Test connection
php artisan lockbox:test
# List all secrets
php artisan lockbox:list
# Show secret details
php artisan lockbox:show <secret-id> --payload
# Create new secret
php artisan lockbox:create my-secret --description="My secret"
# Add version with values
php artisan lockbox:add-version <secret-id> \
--entry=KEY1=value1 \
--entry=KEY2=value2
# Delete secret
php artisan lockbox:delete <secret-id>The library provides specific exceptions for different error types:
use Tigusigalpa\YandexLockbox\Exceptions\AuthenticationException;
use Tigusigalpa\YandexLockbox\Exceptions\NotFoundException;
use Tigusigalpa\YandexLockbox\Exceptions\RateLimitException;
use Tigusigalpa\YandexLockbox\Exceptions\ValidationException;
use Tigusigalpa\YandexLockbox\Exceptions\LockboxException;
try {
$payload = $client->getPayload('secret-id');
} catch (AuthenticationException $e) {
// Handle 401/403 errors
echo "Authentication failed: " . $e->getMessage();
} catch (NotFoundException $e) {
// Handle 404 errors
echo "Secret not found: " . $e->getMessage();
} catch (RateLimitException $e) {
// Handle 429 errors
echo "Rate limit exceeded: " . $e->getMessage();
} catch (ValidationException $e) {
// Handle 400 errors
echo "Validation error: " . $e->getMessage();
} catch (LockboxException $e) {
// Handle other errors
echo "API error: " . $e->getMessage();
print_r($e->getContext());
}Runs complete test suite with 8 tests:
# Basic run
php artisan lockbox:test
# With specific folder
php artisan lockbox:test --folder=b1g8dn6s4f5h6j7k8l9m
# With automatic cleanup
php artisan lockbox:test --cleanupOutput:
π Testing Yandex Lockbox Connection
==================================================
β Test 1: Listing secrets
β Test 2: Creating test secret
Secret ID: e6q7r8s9t0u1v2w3x4y5
β Test 3: Getting secret details
β Test 4: Adding version with payload
β Test 5: Getting secret payload
β Test 6: Listing versions
β Test 7: Deactivating secret
β Test 8: Activating secret
==================================================
β
Tests passed: 8
π All tests passed successfully!
# List in default folder
php artisan lockbox:list
# List in specific folder
php artisan lockbox:list --folder=b1g8dn6s4f5h6j7k8l9m
# Limit results
php artisan lockbox:list --page-size=10# Metadata only
php artisan lockbox:show e6q7r8s9t0u1v2w3x4y5
# With payload
php artisan lockbox:show e6q7r8s9t0u1v2w3x4y5 --payload
# Specific version
php artisan lockbox:show e6q7r8s9t0u1v2w3x4y5 --payload --version=version-id# Simple creation
php artisan lockbox:create my-secret
# With description
php artisan lockbox:create my-secret --description="Production API keys"
# With labels
php artisan lockbox:create my-secret \
--label=env=production \
--label=service=api
# With specific folder
php artisan lockbox:create my-secret --folder=b1g8dn6s4f5h6j7k8l9m# Interactive mode
php artisan lockbox:add-version e6q7r8s9t0u1v2w3x4y5
# With parameters
php artisan lockbox:add-version e6q7r8s9t0u1v2w3x4y5 \
--entry=DB_HOST=localhost \
--entry=DB_USER=admin \
--entry=DB_PASSWORD=secret
# From JSON file
php artisan lockbox:add-version e6q7r8s9t0u1v2w3x4y5 --file=secrets.jsonJSON file format:
{
"payloadEntries": [
{
"key": "API_KEY",
"textValue": "my-key"
},
{
"key": "API_SECRET",
"textValue": "my-secret"
}
]
}# With confirmation
php artisan lockbox:delete e6q7r8s9t0u1v2w3x4y5
# Without confirmation
php artisan lockbox:delete e6q7r8s9t0u1v2w3x4y5 --force# 1. Check connection
php artisan lockbox:test
# 2. View existing secrets
php artisan lockbox:list
# 3. View specific secret
php artisan lockbox:show <secret-id> --payload# 1. Create secret
php artisan lockbox:create production-db \
--description="Production database credentials" \
--label=env=production
# 2. Add values
php artisan lockbox:add-version <secret-id> \
--entry=DB_HOST=prod-db.example.com \
--entry=DB_USER=prod_user \
--entry=DB_PASSWORD=secure_password
# 3. Verify
php artisan lockbox:show <secret-id> --payload# 1. View current version
php artisan lockbox:show <secret-id> --payload
# 2. Add new version
php artisan lockbox:add-version <secret-id> \
--entry=DB_PASSWORD=new_password
# 3. Verify new version
php artisan lockbox:show <secret-id> --payload# Run tests
composer test
# Run tests with coverage
composer test-coveragegetIamToken(): string- Get IAM token (cached automatically)listClouds(): array- List all cloudsgetFirstCloud(): array- Get first cloudgetFirstCloudId(): string- Get first cloud ID
listFolders(string $cloudId): array- List folders in cloudgetFolder(string $folderId): array- Get folder detailsgetFirstFolder(string $cloudId): array- Get first foldergetFirstFolderId(string $cloudId): string- Get first folder IDgetFirstFolderIdFromFirstCloud(): string- Get first folder ID from first cloudcreateFolder(string $iamToken, string $cloudId, string $name, ?string $description = null): array- Create folder
assignRoleToFolder(string $iamToken, string $folderId, string $subjectId, string $role = 'lockbox.editor', string $subjectType = 'userAccount', bool $waitForCompletion = false, int $maxWaitSeconds = 60): array -
Assign role to folder
listFolderAccessBindings(string $iamToken, string $folderId, int $pageSize = 100, ?string $pageToken = null): array -
List folder access bindings (paginated)
getAllFolderAccessBindings(string $iamToken, string $folderId): array- Get all folder access bindings ( auto-pagination)
getUserByLogin(string $login): array- Get full user info by Yandex logingetUserIdByLogin(string $login): string- Get user ID (Subject ID) by Yandex login
waitForOperation(string $iamToken, string $operationId, int $maxWaitSeconds = 60, int $pollIntervalSeconds = 2): array -
Wait for operation to complete
getOperation(string $iamToken, string $operationId): array- Get operation status
listSecrets(string $folderId): array- List secrets in foldergetSecret(string $secretId): array- Get secret metadatacreateSecret(array $data): array- Create new secretupdateSecret(string $secretId, array $data): array- Update secretdeleteSecret(string $secretId): void- Delete secret
addVersion(string $secretId, array $data): array- Add new version to secretgetPayload(string $secretId, ?string $versionId = null): array- Get secret payload
- PHP 8.0 or higher
- Laravel 8.x - 12.x (optional, for Laravel integration)
- Guzzle HTTP client 7.x or 8.x
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This package is open-sourced software licensed under the MIT license.
Igor Sazonov
- GitHub: @tigusigalpa
- Email: sovletig@gmail.com
