[SECURITY] Sensitive data got saved unencrypted in the database

This commit is contained in:
Jeroen De Meerleer 2022-05-20 14:48:22 +02:00
parent 11eb7f47c9
commit 80131f0e99
Signed by: JeroenED
GPG Key ID: 28CCCB8F62BFADD6
6 changed files with 37 additions and 107 deletions

View File

@ -47,7 +47,6 @@ class RunCommand extends Command
}
$jobRepo->setJobRunning($job, true);
$jobRepo->setTempVar($job, 'consolerun', true);
$jobRepo->parseJob($job);
$result = $jobRepo->runNow($job, true);
if($job->getData('crontype') == 'reboot') {
$sleeping = true;

View File

@ -28,7 +28,6 @@ class JobController extends AbstractController
if($request->getMethod() == 'GET') {
$job = $jobRepo->find($id);
$jobRepo->parseJob($job);
$runs = $runRepo->getRunsForJob($job, $all != 'all');
return $this->render('job/view.html.twig', ['job' => $job, 'runs' => $runs, 'allruns' => $all == 'all']);
} elseif($request->getMethod() == 'DELETE') {
@ -44,7 +43,6 @@ class JobController extends AbstractController
if($request->getMethod() == 'GET') {
$jobRepo = $doctrine->getRepository(Job::class);
$job = $jobRepo->find($id);
$jobRepo->parseJob($job, true);
return $this->render('job/edit.html.twig', ['job' => $job]);
} elseif($request->getMethod() == 'POST') {
$allValues = $request->request->all();

View File

@ -187,16 +187,17 @@ class JobRepository extends EntityRepository
{
$client = new Client();
$url = $job->getData('url');
$user = $job->getData('basicauth-username');
if(!empty($job->getData('vars'))) {
foreach($job->getData('vars') as $key => $var) {
if (!empty($job->getData('basicauth-username'))) $job->setData('basicauth-username', str_replace('{' . $key . '}', $var['value'], $job->getData('basicauth-username')));
$job->setData('url', str_replace('{' . $key . '}', $var['value'], $job->getData('url')));
if (!empty($user)) $user = str_replace('{' . $key . '}', ($var['issecret'] ? Secret::decrypt(base64_decode($var['value'])) : $var['value']), $job->getData('basicauth-username'));
$url = str_replace('{' . $key . '}', ($var['issecret'] ? Secret::decrypt(base64_decode($var['value'])) : $var['value']), $job->getData('url'));
}
}
$url = $job->getData('url');
$options['http_errors'] = false;
$options['auth'] = !empty($job->getData('basicauth-username')) ? [$job->getData('basicauth-username'), $job->getData('basicauth-password')] : NULL;
$options['auth'] = !empty($user) ? [$user, Secret::decrypt(base64_decode($job->getData('basicauth-password')))] : NULL;
try {
$res = $client->request('GET', $url, $options);
$return['exitcode'] = $res->getStatusCode();
@ -217,13 +218,13 @@ class JobRepository extends EntityRepository
*/
private function runCommandJob(Job &$job): array
{
$command = $job->getData('command');
if(!empty($job->getData('vars'))) {
foreach ($job->getData('vars') as $key => $var) {
$job->setData('command', str_replace('{' . $key . '}', $var['value'], $job->getData('command')));
$command = str_replace('{' . $key . '}', $var['value'], $job->getData('command'));
}
}
$command = $job->getData('command');
if ($job->getData('containertype') == 'docker') {
$command = $this->prepareDockerCommand($command, $job->getData('service'), $job->getData('container-user'));
}
@ -231,7 +232,7 @@ class JobRepository extends EntityRepository
if($job->getData('hosttype') == 'local') {
$return = $this->runLocalCommand($command);
} elseif($job->getData('hosttype') == 'ssh') {
$return = $this->runSshCommand($command, $job->getData('host'), $job->getData('user'), $job->getData('ssh-privkey'), $job->getData('privkey-password'));
$return = $this->runSshCommand($command, $job->getData('host'), $job->getData('user'), Secret::decrypt(base64_decode($job->getData('ssh-privkey'))), Secret::decrypt(base64_decode($job->getData('privkey-password'))));
}
$return['failed'] = !in_array($return['exitcode'], $job->getData('response'));
} catch (\RuntimeException $exception) {
@ -272,9 +273,9 @@ class JobRepository extends EntityRepository
$key = null;
if(!empty($privkey)) {
if(!empty($password)) {
$key = PublicKeyLoader::load(base64_decode($privkey), $password);
$key = PublicKeyLoader::load($privkey, $password);
} else {
$key = PublicKeyLoader::load(base64_decode($privkey));
$key = PublicKeyLoader::load($privkey);
}
} elseif (!empty($password)) {
$key = $password;
@ -322,7 +323,7 @@ class JobRepository extends EntityRepository
if($job->getData('hosttype') == 'local') {
$this->runLocalCommand($rebootcommand);
} elseif($job->getData('hosttype') == 'ssh') {
$this->runSshCommand($rebootcommand, $job->getData('host'), $job->getData('user'), $job->getData('ssh-privkey') ?? '', $job->getData('privkey-password') ?? '');
$this->runSshCommand($rebootcommand, $job->getData('host'), $job->getData('user'), Secret::decrypt(base64_decode($job->getData('ssh-privkey'))) ?? '', Secret::decrypt(base64_decode($job->getData('privkey-password'))) ?? '');
}
} catch (\RuntimeException $exception) {
$return['exitcode'] = $exception->getCode();
@ -343,16 +344,17 @@ class JobRepository extends EntityRepository
$this->setJobRunning($job, true);
$getservicescommand = $job->getData('getservices-command');
if (!empty($job->getData('vars'))) {
foreach ($job->getData('vars') as $key => $var) {
$job->setData('getservices-command', str_replace('{' . $key . '}', $var['value'], $job->getData('getservices-command')));
$getservicescommand = str_replace('{' . $key . '}', $var['value'], $job->getData('getservices-command'));
}
}
try {
if($job->getData('hosttype') == 'local') {
$return = $this->runLocalCommand($job->getData('getservices-command'));
$return = $this->runLocalCommand($getservicescommand);
} elseif($job->getData('hosttype') == 'ssh') {
$return = $this->runSshCommand($job->getData('getservices-command'), $job->getData('host'), $job->getData('user'), $job->getData('ssh-privkey') ?? '', $job->getData('privkey-password') ?? '');
$return = $this->runSshCommand($getservicescommand, $job->getData('host'), $job->getData('user'), Secret::decrypt(base64_decode($job->getData('ssh-privkey'))) ?? '', Secret::decrypt(base64_decode($job->getData('privkey-password'))) ?? '');
}
} catch (\RuntimeException $exception) {
$return['exitcode'] = $exception->getCode();
@ -374,7 +376,6 @@ class JobRepository extends EntityRepository
*/
public function runNow(Job &$job, $console = false) {
$em = $this->getEntityManager();
$this->parseJob($job, true);
$runRepo = $this->getEntityManager()->getRepository(Run::class);
if($console == false && ($runRepo->isSlowJob($job)) || count($job->getRuns()) == 0 || $job->getData('crontype') === 'reboot') {
@ -422,7 +423,6 @@ class JobRepository extends EntityRepository
{
$em = $this->getEntityManager();
$starttime = microtime(true);
$this->parseJob($job, true);
if ($job->getData('crontype') == 'http') {
$result = $this->runHttpJob($job);
} elseif ($job->getData('crontype') == 'command') {
@ -593,17 +593,8 @@ class JobRepository extends EntityRepository
$job->setData('getservices-command', $values['getservices-command']);
$job->setData('getservices-response', explode(',',$values['getservices-response']));
$job->setData('reboot-duration', $values['reboot-duration']);
if(!empty($values['reboot-delay']) || $values['reboot-delay'] == 0) {
$newsecretkey = count($values['var-value']);
$values['var-id'][$newsecretkey] = 'reboot-delay';
$values['var-issecret'][$newsecretkey] = false;
$values['var-value'][$newsecretkey] = (int)$values['reboot-delay'];
$newsecretkey = count($values['var-value']);
$values['var-id'][$newsecretkey] = 'reboot-delay-secs';
$values['var-issecret'][$newsecretkey] = false;
$values['var-value'][$newsecretkey] = (int)$values['reboot-delay'] * 60;
}
$job->setData('reboot-delay', (int)$values['reboot-delay']);
$job->setData('reboot-delay-secs', (int)$values['reboot-delay'] * 60);
break;
case 'http':
$parsedUrl = parse_url($values['url']);
@ -614,10 +605,7 @@ class JobRepository extends EntityRepository
throw new \InvalidArgumentException('Some data was invalid');
}
if(!empty($values['basicauth-password'])) {
$newsecretkey = count($values['var-value']);
$values['var-id'][$newsecretkey] = 'basicauth-password';
$values['var-issecret'][$newsecretkey] = true;
$values['var-value'][$newsecretkey] = $values['basicauth-password'];
$job->setData('basicauth-password', base64_encode(Secret::encrypt($values['basicauth-password'])));
}
$job->setData('host', $parsedUrl['host']);
break;
@ -633,32 +621,21 @@ class JobRepository extends EntityRepository
case 'ssh':
$job->setData('host', $values['host']);
$job->setData('user', $values['user']);
$job->removeData('privkey-password');
if(!empty($values['privkey-password'])) {
$newsecretkey = count($values['var-value']);
$values['var-id'][$newsecretkey] = 'privkey-password';
$values['var-issecret'][$newsecretkey] = true;
$values['var-value'][$newsecretkey] = $values['privkey-password'];
$job->setData('privkey-password', base64_encode(Secret::encrypt($values['privkey-password'])));
}
$privkeyid = NULL;
if(!empty($_FILES['privkey']['tmp_name'])) {
$newsecretkey = count($values['var-value']);
$privkeyid = $newsecretkey;
$values['var-id'][$newsecretkey] = 'ssh-privkey';
$values['var-issecret'][$newsecretkey] = true;
$values['var-value'][$newsecretkey] = base64_encode(file_get_contents($_FILES['privkey']['tmp_name']));
$job->setData('ssh-privkey', base64_encode(Secret::encrypt(file_get_contents($_FILES['privkey']['tmp_name']))));
}
if(isset($values['privkey-keep']) && $values['privkey-keep'] == true) {
$privkeyid = ($privkeyid === NULL) ? count($values['var-value']) : $privkeyid ;
$values['var-id'][$privkeyid] = 'ssh-privkey';
$values['var-issecret'][$privkeyid] = true;
$values['var-value'][$privkeyid] = $values['privkey-orig'];
$job->setData('ssh-privkey', base64_encode(Secret::encrypt($values['privkey-orig'])));
}
break;
}
switch($job->getData('hosttype')) {
switch($job->getData('containertype')) {
default:
if($job->getData('crontype') == 'http' || $job->getData('crontype') == 'reboot' ) break;
$job->setData('containertype', 'none');
@ -670,7 +647,7 @@ class JobRepository extends EntityRepository
$job->setData('container-user', $values['container-user']);
break;
}
$job->removeData('vars');
if(!empty($values['var-value'])) {
foreach($values['var-value'] as $key => $value) {
if(!empty($value) || $value == 0) {
@ -687,53 +664,6 @@ class JobRepository extends EntityRepository
return $job;
}
/**
* @param int $id
* @param bool $withSecrets
* @return Job|mixed|object|null
*/
public function parseJob(Job &$job, bool $withSecrets = false): void
{
if(!empty($job->getData('vars'))) {
foreach ($job->getData('vars') as $key => &$value) {
if ($value['issecret']) {
$job->setData('vars.' . $key . '.value', ($withSecrets) ? Secret::decrypt(base64_decode($value['value'])) : '');
}
}
}
switch($job->getData('crontype')) {
case 'http':
if($job->hasData('vars.basicauth-password.value')) {
$job->setData('basicauth-password', $job->getData('vars.basicauth-password.value'));
$job->removeData('vars.basicauth-password');
}
break;
case 'reboot':
$job->setData('reboot-delay', $job->getData('vars.reboot-delay.value'));
$job->setData('reboot-delay-secs', $job->getData('vars.reboot-delay-secs.value'));
$job->removeData('vars.reboot-delay');
$job->removeData('vars.reboot-delay-secs');
break;
}
switch($job->getData('hosttype')) {
case 'ssh':
if($job->hasData('vars.ssh-privkey.value')) {
$job->setData('ssh-privkey', $job->getData('vars.ssh-privkey.value'));
$job->removeData('vars.ssh-privkey');
}
if($job->hasData('vars.privkey-password.value')) {
$job->setData('privkey-password', $job->getData('vars.privkey-password.value'));
$job->removeData('vars.privkey-password');
}
break;
}
if($job->getData('crontype') == 'http') {
}
}
/**
* @param int $id
* @return array

View File

@ -90,7 +90,6 @@ class RunRepository extends EntityRepository
} else {
foreach($jobids as $jobid) {
$job = $jobRepo->find($jobid);
$jobRepo->parseJob($job);
$allJobs[] = $job;
}
}

View File

@ -1,6 +1,7 @@
<?php
namespace App\Twig;
use App\Service\Secret;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
@ -11,6 +12,7 @@ class AppExtension extends AbstractExtension
return [
new TwigFilter('interval', [$this, 'parseInterval']),
new TwigFilter('parsetags', [$this, 'parseTags']),
new TwigFilter('decryptsecret', [$this, 'decryptSecret']),
];
}
@ -67,4 +69,9 @@ class AppExtension extends AbstractExtension
return 'dark';
}
}
function decryptSecret(string $text) {
return Secret::decrypt(base64_decode($text));
}
}

View File

@ -137,10 +137,7 @@
<h4>Reboot job details</h4>
<div class="mb-3">
<label for="reboot-command">Reboot command</label>
<input type="text" name="reboot-command" class="form-control" id="reboot-command" placeholder="systemctl reboot" value="
{% if attribute(job.data, 'reboot-command') is defined %}
{{ attribute(job.data, 'reboot-command') }}
{% endif %}">
<input type="text" name="reboot-command" class="form-control" id="reboot-command" placeholder="systemctl reboot" value="{% if attribute(job.data, 'reboot-command') is defined %}{{ attribute(job.data, 'reboot-command') }}{% endif %}">
<small id="reboot-command-help" class="form-text text-muted">Use {reboot-delay} or {reboot-delay-secs} to add the delay in your command</small>
</div>
@ -179,7 +176,7 @@
</div>
<div class="mb-3">
<label for="basicauth-password">Password for Basic-Auth</label>
<input type="password" name="basicauth-password" class="form-control" placeholder="correct horse battery staple" value="{% if attribute(job.data, 'basicauth-password') is defined %}{{ attribute(job.data, 'basicauth-password') }}{% endif %}">
<input type="password" name="basicauth-password" class="form-control" placeholder="correct horse battery staple" value="{% if attribute(job.data, 'basicauth-password') is defined %}{{ attribute(job.data, 'basicauth-password') | decryptsecret }}{% endif %}">
<small id="basicauth-password-help" class="form-text text-muted">This field is being saved as a secret</small>
</div>
@ -210,9 +207,9 @@
<label for="privkey">Private key</label>
<div class="input-group">
<span class=" input-group-text border-end-0">
<input type="checkbox" name="privkey-keep" class="privkey-keep" value="true" data-privkey="{% if attribute(job.data, 'ssh-privkey') is defined %}{{ attribute(job.data, 'ssh-privkey') }}{% endif %}" checked>
<input type="checkbox" name="privkey-keep" class="privkey-keep" value="true" data-privkey="{% if attribute(job.data, 'ssh-privkey') is defined %}{{ attribute(job.data, 'ssh-privkey') | decryptsecret }}{% endif %}" checked>
</span>
<input type="hidden" name="privkey-orig" class="privkey-orig" value="{% if attribute(job.data, 'ssh-privkey') is defined %}{{ attribute(job.data, 'ssh-privkey') }}{% endif %}">
<input type="hidden" name="privkey-orig" class="privkey-orig" value="{% if attribute(job.data, 'ssh-privkey') is defined %}{{ attribute(job.data, 'ssh-privkey') | decryptsecret }}{% endif %}">
<span class="input-group-text border-start-0">Keep</span>
<input type="file" id="privkey" name="privkey" class="form-control " disabled>
</div>
@ -221,7 +218,7 @@
<div class="mb-3">
<label for="privkey-password">Password for private key</label>
<input type="password" name="privkey-password" class="form-control" placeholder="correct horse battery staple" value="{% if attribute(job.data, 'privkey-password') is defined %}{{ attribute(job.data, 'privkey-password') }}{% endif %}">
<input type="password" name="privkey-password" class="form-control" placeholder="correct horse battery staple" value="{% if attribute(job.data, 'privkey-password') is defined %}{{ attribute(job.data, 'privkey-password') | decryptsecret }}{% endif %}">
<small id="privkey-password-help" class="form-text text-muted">If private key is empty this field is being used as ssh-password</small>
<small id="privkey-password-help-2" class="form-text text-muted">This field is being saved as a secret</small>
</div>
@ -263,7 +260,7 @@
</div>
<span class="input-group-text border-start-0">Secret</span>
<input type="text" name="var-id[{{ key }}]" class="form-control var-id" placeholder="name" value="{{ id }}">
<input type="{% if var.issecret %}password{% else %}text{% endif %}" name="var-value[{{ key }}]" class="form-control var-value" placeholder="value" value="{{ var.value }}">
<input type="{% if var.issecret %}password{% else %}text{% endif %}" name="var-value[{{ key }}]" class="form-control var-value" placeholder="value" value="{% if var.issecret %}{{ var.value | decryptsecret }}{% else %}{{ var.value }}{% endif %}">
</div>
{% endfor %}
{% endif %}