From 80131f0e99c31c0f66ba9c3059495d18b28bdd13 Mon Sep 17 00:00:00 2001 From: Jeroen De Meerleer Date: Fri, 20 May 2022 14:48:22 +0200 Subject: [PATCH] [SECURITY] Sensitive data got saved unencrypted in the database --- src/Command/RunCommand.php | 1 - src/Controller/JobController.php | 2 - src/Repository/JobRepository.php | 118 +++++++------------------------ src/Repository/RunRepository.php | 1 - src/Twig/AppExtension.php | 7 ++ templates/job/edit.html.twig | 15 ++-- 6 files changed, 37 insertions(+), 107 deletions(-) diff --git a/src/Command/RunCommand.php b/src/Command/RunCommand.php index d476772..32404e2 100644 --- a/src/Command/RunCommand.php +++ b/src/Command/RunCommand.php @@ -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; diff --git a/src/Controller/JobController.php b/src/Controller/JobController.php index 0b32169..e73358e 100644 --- a/src/Controller/JobController.php +++ b/src/Controller/JobController.php @@ -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(); diff --git a/src/Repository/JobRepository.php b/src/Repository/JobRepository.php index fba1bc0..b6331bc 100644 --- a/src/Repository/JobRepository.php +++ b/src/Repository/JobRepository.php @@ -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 diff --git a/src/Repository/RunRepository.php b/src/Repository/RunRepository.php index b5a9250..775e239 100644 --- a/src/Repository/RunRepository.php +++ b/src/Repository/RunRepository.php @@ -90,7 +90,6 @@ class RunRepository extends EntityRepository } else { foreach($jobids as $jobid) { $job = $jobRepo->find($jobid); - $jobRepo->parseJob($job); $allJobs[] = $job; } } diff --git a/src/Twig/AppExtension.php b/src/Twig/AppExtension.php index 2dd58a7..4184d60 100644 --- a/src/Twig/AppExtension.php +++ b/src/Twig/AppExtension.php @@ -1,6 +1,7 @@ Reboot job details
- + Use {reboot-delay} or {reboot-delay-secs} to add the delay in your command
@@ -179,7 +176,7 @@
- + This field is being saved as a secret
@@ -210,9 +207,9 @@
- + - + Keep
@@ -221,7 +218,7 @@
- + If private key is empty this field is being used as ssh-password This field is being saved as a secret
@@ -263,7 +260,7 @@ Secret - + {% endfor %} {% endif %}