Skip to content

Commit b7a50db

Browse files
oleksandr-ncandrey18106nextcloud-command
authored
feat: added ability to disable FRP for HaRP<-->ExApp communication. (#555)
Signed-off-by: Oleksander Piskun <oleksandr2088@icloud.com> Signed-off-by: Andrey Borysenko <andrey18106x@gmail.com> Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com> Co-authored-by: Andrey Borysenko <andrey18106x@gmail.com> Co-authored-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
1 parent a9b0844 commit b7a50db

12 files changed

Lines changed: 80 additions & 24 deletions

js/app_api-adminSettings.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/app_api-adminSettings.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/Command/Daemon/RegisterDaemon.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ protected function configure(): void {
4848
$this->addOption('harp_frp_address', null, InputOption::VALUE_REQUIRED, '[host]:[port] of the HaRP FRP server, default host is same as HaRP host and port is 8782');
4949
$this->addOption('harp_shared_key', null, InputOption::VALUE_REQUIRED, 'HaRP shared key for secure communication between HaRP and AppAPI');
5050
$this->addOption('harp_docker_socket_port', null, InputOption::VALUE_REQUIRED, '\'remotePort\' of the FRP client of the remote docker socket proxy. There is one included in the harp container so this can be skipped for default setups.', '24000');
51+
$this->addOption('harp_exapp_direct', null, InputOption::VALUE_NONE, 'Flag for the advanced setups only. Disables the FRP tunnel between ExApps and HaRP.');
5152

5253
$this->addUsage('harp_proxy_docker "Harp Proxy (Docker)" "docker-install" "http" "appapi-harp:8780" "http://nextcloud.local" --net nextcloud --harp --harp_frp_address "appapi-harp:8782" --harp_shared_key "some_very_secure_password" --set-default --compute_device=cuda');
5354
$this->addUsage('harp_proxy_host "Harp Proxy (Host)" "docker-install" "http" "localhost:8780" "http://nextcloud.local" --harp --harp_frp_address "localhost:8782" --harp_shared_key "some_very_secure_password" --set-default --compute_device=cuda');
@@ -104,6 +105,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
104105
$deployConfig['harp'] = [
105106
'frp_address' => $input->getOption('harp_frp_address') ?? '',
106107
'docker_socket_port' => $input->getOption('harp_docker_socket_port'),
108+
'exapp_direct' => (bool)$input->getOption('harp_exapp_direct'),
107109
];
108110
}
109111

lib/Controller/HarpController.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,13 @@
1111

1212
use OCA\AppAPI\AppInfo\Application;
1313
use OCA\AppAPI\Db\ExApp;
14-
use OCA\AppAPI\Service\DaemonConfigService;
1514
use OCA\AppAPI\Service\ExAppService;
1615
use OCA\AppAPI\Service\HarpService;
1716
use OCP\AppFramework\Controller;
1817
use OCP\AppFramework\Http;
1918
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
2019
use OCP\AppFramework\Http\Attribute\PublicPage;
2120
use OCP\AppFramework\Http\DataResponse;
22-
use OCP\IAppConfig;
2321
use OCP\IGroupManager;
2422
use OCP\IRequest;
2523
use OCP\IUserManager;
@@ -32,15 +30,14 @@ class HarpController extends Controller {
3230

3331
public function __construct(
3432
IRequest $request,
35-
private readonly IAppConfig $appConfig,
3633
private readonly ExAppService $exAppService,
3734
private readonly LoggerInterface $logger,
3835
private readonly IThrottler $throttler,
3936
private readonly IUserManager $userManager,
4037
private readonly IGroupManager $groupManager,
41-
private readonly DaemonConfigService $daemonConfigService,
4238
private readonly ICrypto $crypto,
4339
private readonly ?string $userId,
40+
private readonly HarpService $harpService,
4441
) {
4542
parent::__construct(Application::APP_ID, $request);
4643

@@ -88,7 +85,7 @@ public function getExAppMetadata(string $appId): DataResponse {
8885
return new DataResponse(['message' => 'Harp shared key is not valid'], Http::STATUS_UNAUTHORIZED);
8986
}
9087

91-
return new DataResponse(HarpService::getHarpExApp($exApp));
88+
return new DataResponse($this->harpService->getHarpExApp($exApp));
9289
}
9390

9491
protected function isUserEnabled(string $userId): bool {

lib/DeployActions/DockerActions.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class DockerActions implements IDeployActions {
3737
public const EX_APP_CONTAINER_PREFIX = 'nc_app_';
3838
public const APP_API_HAPROXY_USER = 'app_api_haproxy_user';
3939
public const FRP_TARGET_DIR = '/certs/frp';
40+
public const DEPLOY_ID = 'docker-install';
4041

4142
private Client $guzzleClient;
4243
private bool $useSocket = false; # for `pullImage` function, to detect can be stream used or not.
@@ -59,7 +60,7 @@ public function __construct(
5960
}
6061

6162
public function getAcceptsDeployId(): string {
62-
return 'docker-install';
63+
return self::DEPLOY_ID;
6364
}
6465

6566
public function deployExApp(ExApp $exApp, DaemonConfig $daemonConfig, array $params = []): string {
@@ -177,7 +178,11 @@ private function installParsedCertificates(string $dockerUrl, string $containerI
177178
}
178179

179180
private function installFRPCertificates(DaemonConfig $daemonConfig, string $dockerUrl, string $containerId, string $targetDir): void {
180-
if (!HarpService::isHarp($daemonConfig->getDeployConfig())) {
181+
$deployConfig = $daemonConfig->getDeployConfig();
182+
if (!HarpService::isHarp($deployConfig)) {
183+
return;
184+
}
185+
if (HarpService::isHarpDirectConnect($deployConfig)) {
181186
return;
182187
}
183188
$certificates = $this->harpService->getFrpCertificates($daemonConfig);
@@ -186,7 +191,7 @@ private function installFRPCertificates(DaemonConfig $daemonConfig, string $dock
186191
return;
187192
}
188193
$tempDir = sys_get_temp_dir();
189-
$this->executeCommandInContainer($dockerUrl, $containerId, ['mkdir', '-p', self::FRP_TARGET_DIR]);
194+
$this->executeCommandInContainer($dockerUrl, $containerId, ['mkdir', '-p', $targetDir]);
190195

191196
foreach (['ca_crt', 'client_crt', 'client_key'] as $key) {
192197
$filename = str_replace('_', '.', $key);
@@ -704,7 +709,7 @@ public function buildDeployParams(DaemonConfig $daemonConfig, array $appInfo): a
704709
];
705710

706711
$harpEnvVars = [];
707-
if (isset($deployConfig['harp'])) {
712+
if (isset($deployConfig['harp']) && !HarpService::isHarpDirectConnect($daemonConfig->getDeployConfig())) {
708713
$harpEnvVars['HP_FRP_ADDRESS'] = explode(':', $deployConfig['harp']['frp_address'])[0];
709714
$harpEnvVars['HP_FRP_PORT'] = explode(':', $deployConfig['harp']['frp_address'])[1];
710715
$harpEnvVars['HP_SHARED_KEY'] = $this->crypto->decrypt($deployConfig['haproxy_password']);

lib/DeployActions/ManualActions.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@
1818
*/
1919
class ManualActions implements IDeployActions {
2020

21+
public const DEPLOY_ID = 'manual-install';
22+
2123
public function __construct(
2224
private readonly ExAppService $exAppService,
2325
) {
2426
}
2527

2628
public function getAcceptsDeployId(): string {
27-
return 'manual-install';
29+
return self::DEPLOY_ID;
2830
}
2931

3032
public function deployExApp(ExApp $exApp, DaemonConfig $daemonConfig, array $params = []): string {

lib/Service/AppAPICommonService.php

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,11 @@
1313
use OCA\AppAPI\Db\ExApp;
1414
use OCP\App\IAppManager;
1515
use OCP\IRequest;
16-
use Psr\Log\LoggerInterface;
1716

1817
class AppAPICommonService {
1918

2019
public function __construct(
2120
private readonly IAppManager $appManager,
22-
private readonly DaemonConfigService $daemonConfigService,
23-
private readonly LoggerInterface $logger,
2421
private readonly HarpService $harpService,
2522
) {
2623
}
@@ -36,15 +33,16 @@ public function buildAppAPIAuthHeaders(?IRequest $request, ?string $userId, ExAp
3633

3734
if ($this->harpService->isHarp($exApp->getDeployConfig())) {
3835
$harpKey = $this->harpService->getHarpSharedKey($exApp->getDeployConfig());
39-
$headers['HARP-SHARED-KEY'] = $harpKey;
40-
$headers['EX-APP-PORT'] = $exApp->getPort();
36+
$headers['harp-shared-key'] = $harpKey;
37+
$headers['ex-app-port'] = $exApp->getPort();
38+
$headers['ex-app-host'] = $this->harpService->getExAppHost($exApp);
4139
}
4240

4341
return $headers;
4442
}
4543

4644
public function buildExAppHost(array $deployConfig): string {
47-
if (boolval($deployConfig['harp'] ?? false)) {
45+
if (($deployConfig['harp'] ?? false) && !HarpService::isHarpDirectConnect($deployConfig)) {
4846
return '127.0.0.1';
4947
}
5048
if (isset($deployConfig['additional_options']['OVERRIDE_APP_HOST'])) {

lib/Service/DaemonConfigService.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use OCA\AppAPI\Db\DaemonConfig;
1313
use OCA\AppAPI\Db\DaemonConfigMapper;
1414

15+
use OCA\AppAPI\DeployActions\ManualActions;
1516
use OCP\AppFramework\Db\DoesNotExistException;
1617
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
1718
use OCP\DB\Exception;
@@ -31,6 +32,10 @@ public function __construct(
3132
}
3233

3334
public function registerDaemonConfig(array $params): ?DaemonConfig {
35+
if (!isset($params['deploy_config']['net'])) {
36+
$this->logger->error('Failed to register daemon configuration: `net` key should be present in the deploy config.');
37+
return null;
38+
}
3439
$bad_patterns = ['http', 'https', 'tcp', 'udp', 'ssh'];
3540
$docker_host = (string)$params['host'];
3641
$frp_host = (string)($params['harp']['frp_address'] ?? '');
@@ -45,9 +50,15 @@ public function registerDaemonConfig(array $params): ?DaemonConfig {
4550
}
4651
}
4752
if ($params['protocol'] !== 'http' && $params['protocol'] !== 'https') {
48-
$this->logger->error('Failed to register daemon configuration. `protocol` must be `http` or `https`.');
53+
$this->logger->error('Failed to register daemon configuration: `protocol` must be `http` or `https`.');
4954
return null;
5055
}
56+
if ($params['accepts_deploy_id'] !== ManualActions::DEPLOY_ID && isset($params['deploy_config']['harp']['exapp_direct']) && $params['deploy_config']['harp']['exapp_direct'] === true) {
57+
if ($params['deploy_config']['net'] === 'host') {
58+
$this->logger->error('Failed to register daemon configuration: setting `net=host` in HaRP is not supported when communication with ExApps is done directly without FRP.');
59+
return null;
60+
}
61+
}
5162
$params['deploy_config']['nextcloud_url'] = rtrim($params['deploy_config']['nextcloud_url'], '/');
5263
try {
5364
if (isset($params['deploy_config']['haproxy_password']) && $params['deploy_config']['haproxy_password'] !== '') {

lib/Service/HarpService.php

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use GuzzleHttp\Exception\ClientException;
1414
use OCA\AppAPI\Db\DaemonConfig;
1515
use OCA\AppAPI\Db\ExApp;
16+
use OCA\AppAPI\DeployActions\ManualActions;
1617
use OCP\ICertificateManager;
1718
use OCP\IConfig;
1819
use OCP\Security\ICrypto;
@@ -26,8 +27,6 @@ public function __construct(
2627
private readonly IConfig $config,
2728
private readonly ICertificateManager $certificateManager,
2829
private readonly ICrypto $crypto,
29-
private readonly ExAppService $exAppService,
30-
private readonly DaemonConfigService $daemonConfigService,
3130
) {
3231
}
3332

@@ -70,7 +69,7 @@ protected function initGuzzleClient(DaemonConfig $daemonConfig): void {
7069
}
7170

7271
$harpKey = $this->crypto->decrypt($daemonConfig->getDeployConfig()['haproxy_password']);
73-
if (boolval($daemonConfig->getDeployConfig()['harp'] ?? false)) {
72+
if ($daemonConfig->getDeployConfig()['harp'] ?? false) {
7473
$guzzleParams['headers'] = [
7574
'harp-shared-key' => $harpKey,
7675
];
@@ -88,10 +87,33 @@ public static function isHarp(array $deployConfig): bool {
8887
return boolval($deployConfig['harp'] ?? false);
8988
}
9089

91-
public static function getHarpExApp(ExApp $exApp): array {
90+
public static function isHarpDirectConnect(array $deployConfig): bool {
91+
return isset($deployConfig['harp']['exapp_direct']) && $deployConfig['harp']['exapp_direct'] === true;
92+
}
93+
94+
public static function getExAppHost(ExApp $exApp): string {
95+
$deployConfig = $exApp->getDeployConfig();
96+
if (HarpService::isHarpDirectConnect($deployConfig)) {
97+
if (isset($deployConfig['additional_options']['OVERRIDE_APP_HOST']) &&
98+
$deployConfig['additional_options']['OVERRIDE_APP_HOST'] !== ''
99+
) {
100+
$wideNetworkAddresses = ['0.0.0.0', '127.0.0.1', '::', '::1'];
101+
if (!in_array($deployConfig['additional_options']['OVERRIDE_APP_HOST'], $wideNetworkAddresses)) {
102+
return $deployConfig['additional_options']['OVERRIDE_APP_HOST'];
103+
}
104+
}
105+
if ($exApp->getAcceptsDeployId() !== ManualActions::DEPLOY_ID) {
106+
return $exApp->getAppid();
107+
}
108+
}
109+
return "127.0.0.1";
110+
}
111+
112+
public function getHarpExApp(ExApp $exApp): array {
92113
return [
93114
'exapp_token' => $exApp->getSecret(),
94115
'exapp_version' => $exApp->getVersion(),
116+
'host' => $this->getExAppHost($exApp),
95117
'port' => $exApp->getPort(),
96118
'routes' => array_map(function ($route) {
97119
$bruteforceList = json_decode($route['bruteforce_protection'], true);
@@ -120,7 +142,7 @@ public function harpExAppUpdate(DaemonConfig $daemonConfig, ExApp $exApp, bool $
120142
try {
121143
if ($added) {
122144
$this->guzzleClient->post($url, [
123-
'json' => self::getHarpExApp($exApp),
145+
'json' => $this->getHarpExApp($exApp),
124146
]);
125147
} else {
126148
$this->guzzleClient->delete($url);

src/components/DaemonConfig/DaemonConfigDetailsModal.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
<p><b>{{ t('app_api', 'Name') }}: </b>{{ daemon.name }}</p>
2121
<p><b>{{ t('app_api', 'Protocol') }}: </b>{{ daemon.protocol }}</p>
2222
<p><b>{{ t('app_api', 'Host') }}: </b>{{ daemon.host }}</p>
23+
<p v-if="daemon.deploy_config.harp"><b>{{ t('app_api', 'ExApp direct communication (FRP disabled)') }}: </b>{{ daemon.deploy_config.harp.exapp_direct ?? false }}</p>
2324

2425
<h3>{{ t('app_api', 'Deploy config') }}</h3>
2526
<p><b>{{ t('app_api', 'Docker network') }}: </b>{{ daemon.deploy_config.net }}</p>

0 commit comments

Comments
 (0)