chore: encrypt with casts + masking and interpolation + fixes

This commit is contained in:
David Buday 2024-11-09 19:08:53 +01:00
parent 02d29d93b4
commit ebf207296b
6 changed files with 61 additions and 21 deletions

View file

@ -2477,18 +2477,20 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
private function handleRegistryAuth()
{
$username = escapeshellarg($this->application->docker_registry_username);
$username = $this->application->docker_registry_username;
$registry = $this->application->docker_registry_url ?: 'docker.io';
$token = escapeshellarg($this->application->docker_registry_token);
$registry = escapeshellarg($this->application->docker_registry_url ?: 'docker.io'); // Default to docker.io
$this->application_deployment_queue->addLogEntry('Attempting to log into registry...');
$command = "echo {{secrets.token}} | docker login {$registry} -u {$username} --password-stdin";
$command = "echo {$token} | docker login {$registry} -u {$username} --password-stdin";
$this->execute_remote_command([
$command,
'hidden' => true,
]);
$this->execute_remote_command(
[
'command' => $command,
'secrets' => [
'token' => $token,
],
'hidden' => true,
]
);
}
}

View file

@ -73,8 +73,8 @@ class General extends Component
'application.docker_registry_image_tag' => 'nullable',
'application.docker_use_custom_registry' => 'boolean',
'application.docker_registry_url' => 'nullable',
'application.docker_registry_username' => 'required_with:application.docker_use_custom_registry',
'application.docker_registry_token' => 'required_with:application.docker_use_custom_registry',
'application.docker_registry_username' => 'nullable|required_if:application.docker_use_custom_registry,true',
'application.docker_registry_token' => 'nullable|required_if:application.docker_use_custom_registry,true',
'application.dockerfile_location' => 'nullable',
'application.docker_compose_location' => 'nullable',
'application.docker_compose' => 'nullable',
@ -154,6 +154,7 @@ class General extends Component
$this->application->fqdn = null;
$this->application->settings->save();
}
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
$this->ports_exposes = $this->application->ports_exposes;
$this->is_preserve_repository_enabled = $this->application->settings->is_preserve_repository_enabled;

View file

@ -21,8 +21,8 @@ class DockerImage extends Component
protected $rules = [
'dockerImage' => 'required|string',
'registryUsername' => 'required_with:useCustomRegistry|string',
'registryToken' => 'required_with:useCustomRegistry|string',
'registryUsername' => 'required_if:useCustomRegistry,true|string|nullable',
'registryToken' => 'required_if:useCustomRegistry,true|string|nullable',
'registryUrl' => 'nullable|string',
'useCustomRegistry' => 'boolean'
];
@ -38,8 +38,8 @@ class DockerImage extends Component
{
$this->validate([
'dockerImage' => 'required',
'registryUsername' => 'required_with:useCustomRegistry',
'registryToken' => 'required_with:useCustomRegistry',
'registryUsername' => 'required_if:useCustomRegistry,true',
'registryToken' => 'required_if:useCustomRegistry,true',
]);
// Only save registry settings if useCustomRegistry is true

View file

@ -113,6 +113,10 @@ class Application extends BaseModel
protected $guarded = [];
protected $casts = [
'docker_registry_token' => 'encrypted',
];
protected $appends = ['server_status'];
protected static function booted()

View file

@ -36,6 +36,10 @@ trait ExecuteRemoteCommand
$ignore_errors = data_get($single_command, 'ignore_errors', false);
$append = data_get($single_command, 'append', true);
$this->save = data_get($single_command, 'save');
$secrets = data_get($single_command, 'secrets', []); // Secrets for interpolation and masking
if (count($secrets) > 0) {
$command = $this->interpolateCommand($command, $secrets);
}
if ($this->server->isNonRoot()) {
if (str($command)->startsWith('docker exec')) {
$command = str($command)->replace('docker exec', 'sudo docker exec');
@ -44,10 +48,14 @@ trait ExecuteRemoteCommand
}
}
$remote_command = SshMultiplexingHelper::generateSshCommand($this->server, $command);
$process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden, $customType, $append) {
$process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $secrets, $hidden, $customType, $append) {
$output = str($output)->trim();
if ($output->startsWith('╔')) {
$output = "\n".$output;
if (count($secrets) > 0) {
$output = $this->maskSecrets($output, $secrets);
$command = $this->maskSecrets($command, $secrets);
}
if (str($output)->startsWith('╔')) {
$output = "\n" . $output;
}
$new_log_entry = [
'command' => remove_iip($command),
@ -93,4 +101,29 @@ trait ExecuteRemoteCommand
}
});
}
private function interpolateCommand(string $command, array $secrets): string
{
foreach ($secrets as $key => $value) {
// Define the placeholder format
$placeholder = "{{secrets.$key}}";
// Replace placeholder with actual value
$command = str_replace($placeholder, $value, $command);
}
return $command;
}
private function maskSecrets(string $text, array $secrets): string
{
// Sort secrets by length descending to prevent partial masking
usort($secrets, function ($a, $b) {
return strlen($b) - strlen($a);
});
foreach ($secrets as $value) {
// Replace each secret value with '*****'
$text = str_replace($value, '*****', $text);
}
return $text;
}
}

View file

@ -21,11 +21,11 @@
helper="Leave empty for Docker Hub" />
<x-forms.input id="registryUsername" label="Registry Username"
required="required_with:useCustomRegistry" placeholder="Username for private registry"
required="required_if:useCustomRegistry,true" placeholder="Username for private registry"
helper="Leave empty for public images or server credentials" />
<x-forms.input type="password" id="registryToken" label="Registry Token/Password"
required="required_with:useCustomRegistry" placeholder="Token or password for private registry"
required="required_if:useCustomRegistry,true" placeholder="Token or password for private registry"
helper="Leave empty for public images or server credentials" />
</div>
@endif