Skip to content

Commit 03a4023

Browse files
authored
Merge pull request #1 from dotenv-org/add-dotenv-vault
Add dotenv vault
2 parents 7d55d7e + 1c2d3de commit 03a4023

File tree

3 files changed

+201
-2
lines changed

3 files changed

+201
-2
lines changed

.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
# Environments
3+
.env
4+
.venv
5+
.env.vault
6+
.env.me
7+
8+
.phpunit.result.cache
9+
composer.lock
10+
phpstan.neon
11+
phpstan.tests.neon
12+
phpunit.xml
13+
vendor
14+
15+
.vscode

composer.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "dotenv-org/phpdotenv-vault",
3+
"description": "Loads environment variables from `.env` or `.env.vault`",
4+
"keywords": [
5+
"env",
6+
"dotenv",
7+
"environment",
8+
"environment variables",
9+
"dotenv-vault",
10+
"configurations"
11+
],
12+
"license": "BSD-3-Clause",
13+
"autoload": {
14+
"psr-4": {
15+
"PhpdotenvVault\\": "src/"
16+
}
17+
},
18+
"authors": [
19+
{
20+
"name": "dotenv",
21+
"email": "[email protected]",
22+
"homepage": "https://github.com/dotenv-org"
23+
}
24+
],
25+
"require": {
26+
"php": "^7.1.3 || ^8.0",
27+
"vlucas/phpdotenv": "^5.5"
28+
},
29+
"extra": {
30+
"laravel": {
31+
"providers": [
32+
"PhpdotenvVault\\PhpdotenvVaultServiceProvider"
33+
]
34+
}
35+
},
36+
"require-dev": {
37+
"orchestra/testbench": "^3.8"
38+
}
39+
}

src/DotenvVault.php

Lines changed: 147 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,152 @@
11
<?php
22

33
namespace DotenvVault;
4+
use Dotenv\Dotenv;
5+
use Dotenv\Loader\Loader;
6+
use Dotenv\Loader\LoaderInterface;
7+
use Dotenv\Parser\Parser;
8+
use Dotenv\Parser\ParserInterface;
9+
use Dotenv\Repository\RepositoryBuilder;
10+
use Dotenv\Repository\RepositoryInterface;
11+
use Dotenv\Store\StoreBuilder;
12+
use Dotenv\Store\StoreInterface;
13+
use Exception;
414

5-
class DotenvVault{
15+
class DotEnvVaultError extends Exception { }
16+
17+
class DotEnvVault extends Dotenv
18+
{
19+
private $store;
20+
private $parser;
21+
private $loader;
22+
private $repository;
23+
private $dotenv_key;
24+
public function __construct(
25+
StoreInterface $store,
26+
ParserInterface $parser,
27+
LoaderInterface $loader,
28+
RepositoryInterface $repository
29+
)
30+
{
31+
$this->store = $store;
32+
$this->parser = $parser;
33+
$this->loader = $loader;
34+
$this->repository = $repository;
35+
}
36+
37+
public function load()
38+
{
39+
$this->dotenv_key = getenv("DOTENV_KEY");
40+
if ($this->dotenv_key !== false){
41+
42+
$entries = $this->parser->parse($this->store->read());
43+
$this->loader->load($this->repository, $entries);
44+
45+
$plaintext = $this->parse_vault();
46+
47+
// parsing plaintext and loading to $_ENV
48+
$test_entries = $this->parser->parse($plaintext);
49+
$this->loader->load($this->repository, $test_entries);
50+
}
51+
else {
52+
$entries = $this->parser->parse($this->store->read());
53+
54+
var_dump($entries[0]->getName());
55+
var_dump($entries[0]->getValue());
56+
57+
return $this->loader->load($this->repository, $entries);
58+
}
59+
}
60+
61+
public function parse_vault()
62+
{
63+
$dotenv_keys = explode(',', $this->dotenv_key);
64+
$keys = array();
65+
foreach($dotenv_keys as $key)
66+
{
67+
// parse DOTENV_KEY, format is a URI.
68+
$uri = parse_url(trim($key));
69+
70+
// get encrypted key
71+
$pass = $uri['pass'];
72+
73+
// Get environment from query params.
74+
parse_str($uri['query'], $params);
75+
$vault_environment = $params['environment'] or throw new DotEnvVaultError('INVALID_DOTENV_KEY: Missing environment part.');
76+
77+
# Getting ciphertext from correct environment in .env.vault
78+
$vault_environment = strtoupper($vault_environment);
79+
$environment_key = "DOTENV_VAULT_{$vault_environment}";
80+
81+
$ciphertext = $_ENV["{$environment_key}"] or throw new DotEnvVaultError("NOT_FOUND_DOTENV_ENVIRONMENT: Cannot locate environment {$environment_key} in your .env.vault file. Run 'npx dotenv-vault build' to include it.");
82+
83+
array_push($keys, array('encrypted_key' => $pass, 'ciphertext' => $ciphertext));
84+
}
85+
return $this->key_rotation($keys);
86+
}
87+
88+
private function key_rotation($keys){
89+
$count = count($keys);
90+
foreach($keys as $index=>$value) {
91+
$decrypt = $this->decrypt($value['ciphertext'], $value['encrypted_key']);
92+
93+
if ($decrypt == false && $index + 1 >= $count){
94+
throw new DotEnvVaultError('INVALID_DOTENV_KEY: Key must be valid.');
95+
}
96+
elseif($decrypt == false){
97+
continue;
98+
}
99+
else{
100+
return $decrypt;
101+
}
102+
}
103+
}
104+
105+
private function decrypt($data, $secret)
106+
{
107+
$secret = hex2bin(substr($secret, 4, strlen($secret)));
108+
$data = base64_decode($data, true);
109+
$nonce = substr($data, 0, 12);
110+
$tag = substr($data, -16);
111+
$ciphertext = substr($data, 12, -16);
6112

7-
}
113+
try {
114+
return openssl_decrypt(
115+
$ciphertext,
116+
'aes-256-gcm',
117+
$secret,
118+
OPENSSL_RAW_DATA,
119+
$nonce,
120+
$tag
121+
);
122+
} catch (Exception $e) {
123+
return false;
124+
}
125+
}
126+
127+
public static function createImmutable($paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null)
128+
{
129+
$repository = RepositoryBuilder::createWithDefaultAdapters()->immutable()->make();
130+
131+
return self::create($repository, $paths, $names, $shortCircuit, $fileEncoding);
132+
}
133+
134+
public static function create(RepositoryInterface $repository, $paths, $names = null, bool $shortCircuit = true, string $fileEncoding = null)
135+
{
136+
$builder = $names === null ? StoreBuilder::createWithDefaultName() : StoreBuilder::createWithNoNames();
137+
138+
foreach ((array) $paths as $path) {
139+
$builder = $builder->addPath($path);
140+
}
141+
142+
foreach ((array) $names as $name) {
143+
$builder = $builder->addName($name);
144+
}
145+
146+
if ($shortCircuit) {
147+
$builder = $builder->shortCircuit();
148+
}
149+
150+
return new self($builder->fileEncoding($fileEncoding)->make(), new Parser(), new Loader(), $repository);
151+
}
152+
}

0 commit comments

Comments
 (0)