feat(Production): Complete production deployment infrastructure

- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -5,6 +5,8 @@ declare(strict_types=1);
namespace App\Framework\Vault;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\ValueObjects\SqlQuery;
use App\Framework\DateTime\Clock;
use App\Framework\Http\IpAddress;
use App\Framework\Ulid\Ulid;
use App\Framework\UserAgent\UserAgent;
@@ -35,10 +37,11 @@ final readonly class DatabaseVault implements Vault
private ConnectionInterface $connection,
private string $encryptionKey,
private VaultAuditLogger $auditLogger,
private Clock $clock,
private ?IpAddress $clientIp = null,
private ?UserAgent $userAgent = null
) {
if (!extension_loaded('sodium')) {
if (! extension_loaded('sodium')) {
throw new \RuntimeException('Sodium extension required for DatabaseVault');
}
@@ -51,10 +54,11 @@ final readonly class DatabaseVault implements Vault
public function get(SecretKey $key): SecretValue
{
$row = $this->connection->queryOne(
$query = SqlQuery::create(
'SELECT encrypted_value, encryption_nonce FROM vault_secrets WHERE secret_key = ?',
[$key->value]
);
$row = $this->connection->queryOne($query);
if ($row === null) {
throw VaultKeyNotFoundException::forKey($key);
@@ -100,7 +104,7 @@ final readonly class DatabaseVault implements Vault
$encrypted = $this->encrypt($value->reveal(), $nonce);
$data = [
'id' => Ulid::generate(),
'id' => (string) new Ulid($this->clock),
'secret_key' => $key->value,
'encrypted_value' => base64_encode($encrypted),
'encryption_nonce' => base64_encode($nonce),
@@ -111,17 +115,19 @@ final readonly class DatabaseVault implements Vault
// Upsert: Insert oder Update wenn bereits existiert
if ($this->has($key)) {
$this->connection->update(
$updateQuery = SqlQuery::update(
'vault_secrets',
[
'encrypted_value' => $data['encrypted_value'],
'encryption_nonce' => $data['encryption_nonce'],
'updated_at' => $data['updated_at'],
],
['secret_key' => $key->value]
);
'secret_key = :secret_key'
)->withParameter('secret_key', $key->value);
$this->connection->execute($updateQuery);
} else {
$this->connection->insert('vault_secrets', $data);
$insertQuery = SqlQuery::insert('vault_secrets', $data);
$this->connection->execute($insertQuery);
}
// Audit Log
@@ -147,24 +153,24 @@ final readonly class DatabaseVault implements Vault
public function has(SecretKey $key): bool
{
$result = $this->connection->queryOne(
$query = SqlQuery::create(
'SELECT COUNT(*) as count FROM vault_secrets WHERE secret_key = ?',
[$key->value]
);
$result = $this->connection->queryOne($query);
return ($result['count'] ?? 0) > 0;
}
public function delete(SecretKey $key): void
{
if (!$this->has($key)) {
if (! $this->has($key)) {
throw VaultKeyNotFoundException::forKey($key);
}
$this->connection->delete(
'vault_secrets',
['secret_key' => $key->value]
);
$deleteQuery = SqlQuery::delete('vault_secrets', 'secret_key = :secret_key')
->withParameter('secret_key', $key->value);
$this->connection->execute($deleteQuery);
// Audit Log
$this->auditLogger->log(
@@ -180,22 +186,24 @@ final readonly class DatabaseVault implements Vault
public function all(): array
{
$result = $this->connection->query(
'SELECT secret_key FROM vault_secrets ORDER BY secret_key ASC'
);
$query = SqlQuery::create('SELECT secret_key FROM vault_secrets ORDER BY secret_key ASC');
$result = $this->connection->query($query);
return array_map(
fn (array $row) => $row['secret_key'],
$result
);
$keys = [];
foreach ($result as $row) {
$keys[] = $row['secret_key'];
}
return $keys;
}
public function getMetadata(SecretKey $key): VaultMetadata
{
$row = $this->connection->queryOne(
$query = SqlQuery::create(
'SELECT * FROM vault_secrets WHERE secret_key = ?',
[$key->value]
);
$row = $this->connection->queryOne($query);
if ($row === null) {
throw VaultKeyNotFoundException::forKey($key);
@@ -205,8 +213,8 @@ final readonly class DatabaseVault implements Vault
key: $key,
createdAt: new DateTimeImmutable($row['created_at']),
updatedAt: new DateTimeImmutable($row['updated_at']),
createdBy: $row['created_by'],
updatedBy: $row['updated_by'],
createdBy: $row['created_by'] ?? null,
updatedBy: $row['updated_by'] ?? null,
accessCount: (int) $row['access_count'],
lastAccessedAt: $row['last_accessed_at']
? new DateTimeImmutable($row['last_accessed_at'])
@@ -222,9 +230,8 @@ final readonly class DatabaseVault implements Vault
);
}
$allSecrets = $this->connection->query(
'SELECT secret_key, encrypted_value, encryption_nonce FROM vault_secrets'
);
$query = SqlQuery::create('SELECT secret_key, encrypted_value, encryption_nonce FROM vault_secrets');
$allSecrets = $this->connection->query($query);
$rotatedCount = 0;
@@ -245,15 +252,16 @@ final readonly class DatabaseVault implements Vault
);
// Update in Database
$this->connection->update(
$updateQuery = SqlQuery::update(
'vault_secrets',
[
'encrypted_value' => base64_encode($newEncrypted),
'encryption_nonce' => base64_encode($newNonce),
'updated_at' => (new DateTimeImmutable())->format('Y-m-d H:i:s'),
],
['secret_key' => $row['secret_key']]
);
'secret_key = :secret_key'
)->withParameter('secret_key', $row['secret_key']);
$this->connection->execute($updateQuery);
$rotatedCount++;
@@ -298,13 +306,14 @@ final readonly class DatabaseVault implements Vault
private function updateAccessMetadata(SecretKey $key): void
{
$this->connection->query(
$query = SqlQuery::create(
'UPDATE vault_secrets
SET access_count = access_count + 1,
last_accessed_at = ?
WHERE secret_key = ?',
[(new DateTimeImmutable())->format('Y-m-d H:i:s'), $key->value]
);
$this->connection->execute($query);
}
/**