File size: 3,595 Bytes
b2dcf0f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<?php

namespace App\Domain\Config;

use App\Domain\Audit\AuditService;
use App\Infrastructure\Ids\UuidGenerator;
use App\Shared\Exceptions\ApiException;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;

class SecretMetadataService
{
    public function __construct(
        private readonly UuidGenerator $ids,
        private readonly AuditService $audit,
    ) {
    }

    public function list(): array
    {
        return [
            'secrets' => DB::table('secret_metadata')
                ->whereNull('deleted_at')
                ->orderBy('service_name')
                ->orderBy('secret_name')
                ->get()
                ->map(fn ($row): array => $this->payload((array) $row))
                ->all(),
        ];
    }

    public function create(array $input, string $actorUserId): array
    {
        foreach (['secret_value', 'value', 'password', 'token'] as $forbidden) {
            if (array_key_exists($forbidden, $input)) {
                throw new ApiException('RAW_SECRET_NOT_ALLOWED', 'Raw secret values must not be submitted to secret metadata.', [], 422);
            }
        }

        $id = $this->ids->generate();
        $now = Carbon::now();

        DB::table('secret_metadata')->insert([
            'id' => $id,
            'service_name' => strtoupper($input['service_name']),
            'secret_name' => strtoupper($input['secret_name']),
            'vault_path' => $input['vault_path'],
            'status' => $input['status'] ?? 'ACTIVE',
            'last_rotated_at' => $input['last_rotated_at'] ?? null,
            'next_rotation_due_at' => $input['next_rotation_due_at'] ?? null,
            'created_by_user_id' => $actorUserId,
            'created_at' => $now,
            'updated_at' => $now,
        ]);

        $this->audit->record('SECRET_METADATA_CREATED', $actorUserId, null, 'secret_metadata', $id, 'SUCCESS', [
            'service_name' => strtoupper($input['service_name']),
            'secret_name' => strtoupper($input['secret_name']),
        ]);

        return ['secret' => $this->payload((array) DB::table('secret_metadata')->where('id', $id)->first())];
    }

    public function rotate(string $id, string $actorUserId): array
    {
        $secret = DB::table('secret_metadata')->where('id', $id)->whereNull('deleted_at')->first();

        if ($secret === null) {
            throw new ApiException('SECRET_METADATA_NOT_FOUND', 'Secret metadata record was not found.', [], 404);
        }

        DB::table('secret_metadata')->where('id', $id)->update([
            'status' => 'ROTATED',
            'last_rotated_at' => Carbon::now(),
            'next_rotation_due_at' => Carbon::now()->addDays(90),
            'updated_at' => Carbon::now(),
        ]);

        $this->audit->record('SECRET_METADATA_ROTATED', $actorUserId, null, 'secret_metadata', $id, 'SUCCESS');

        return ['secret' => $this->payload((array) DB::table('secret_metadata')->where('id', $id)->first())];
    }

    private function payload(array $row): array
    {
        return [
            'id' => $row['id'],
            'service_name' => $row['service_name'],
            'secret_name' => $row['secret_name'],
            'vault_path' => $row['vault_path'],
            'status' => $row['status'],
            'last_rotated_at' => $row['last_rotated_at'],
            'next_rotation_due_at' => $row['next_rotation_due_at'],
            'created_by_user_id' => $row['created_by_user_id'],
            'created_at' => $row['created_at'],
            'updated_at' => $row['updated_at'],
        ];
    }
}