Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

フロント画面へのIPアクセス制限の実装 #5858

Merged
merged 12 commits into from Dec 7, 2022
4 changes: 4 additions & 0 deletions app/config/eccube/packages/eccube.yaml
Expand Up @@ -2,6 +2,8 @@ parameters:
# EC-CUBE default env parameters
env(ECCUBE_ADMIN_ROUTE): 'admin'
env(ECCUBE_USER_DATA_ROUTE): 'user_data'
env(ECCUBE_FRONT_ALLOW_HOSTS): '[]'
env(ECCUBE_FRONT_DENY_HOSTS): '[]'
env(ECCUBE_ADMIN_ALLOW_HOSTS): '[]'
env(ECCUBE_ADMIN_DENY_HOSTS): '[]'
env(ECCUBE_FORCE_SSL): '0'
Expand All @@ -24,6 +26,8 @@ parameters:
eccube_mailer_dsn: '%env(MAILER_DSN)%'
eccube_admin_route: '%env(ECCUBE_ADMIN_ROUTE)%'
eccube_user_data_route: '%env(ECCUBE_USER_DATA_ROUTE)%'
eccube_front_allow_hosts: '%env(json:ECCUBE_FRONT_ALLOW_HOSTS)%'
eccube_front_deny_hosts: '%env(json:ECCUBE_FRONT_DENY_HOSTS)%'
eccube_admin_allow_hosts: '%env(json:ECCUBE_ADMIN_ALLOW_HOSTS)%'
eccube_admin_deny_hosts: '%env(json:ECCUBE_ADMIN_DENY_HOSTS)%'
eccube_force_ssl: '%env(bool:ECCUBE_FORCE_SSL)%'
Expand Down
2 changes: 1 addition & 1 deletion codeception/_support/AcceptanceTester.php
Expand Up @@ -79,7 +79,7 @@ public function goToAdminPage($dir = '')
$I = $this;
if ($dir == '') {
$config = Fixtures::get('config');
$I->amOnPage('/'.$config['eccube_admin_route']);
$I->amOnPage('/'.$config['eccube_admin_route'].'/');
} else {
$I->amOnPage('/'.$dir);
}
Expand Down
60 changes: 60 additions & 0 deletions codeception/_support/Page/Admin/SystemSecurityPage.php
@@ -0,0 +1,60 @@
<?php

/*
* This file is part of EC-CUBE
*
* Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
*
* http://www.ec-cube.co.jp/
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Page\Admin;

class SystemSecurityPage extends AbstractAdminPageStyleGuide
{
/**
* @param \AcceptanceTester $I
*/
public static function go($I)
{
$page = new self($I);

return $page->goPage('/setting/system/security', 'セキュリティ管理システム設定');
}

/**
* @param \AcceptanceTester $I
*/
public static function at($I)
{
$page = new self($I);
$page->atPage('セキュリティ管理システム設定');

return $page;
}

public function 入力_front許可リスト($ip)
{
$this->tester->fillField(['id' => 'admin_security_front_allow_hosts'], $ip);

return $this;
}

public function 入力_front拒否リスト($ip)
{
$this->tester->fillField(['id' => 'admin_security_front_deny_hosts'], $ip);

return $this;
}

public function 登録()
{
$this->tester->click('#page_admin_setting_system_security form div.c-contentsArea__cols > div.c-conversionArea > div > div > div:nth-child(2) > div > div > button');
$this->tester->see('保存しました');

return $this;
}
}
8 changes: 8 additions & 0 deletions codeception/_support/Page/Front/TopPage.php
Expand Up @@ -25,6 +25,14 @@ public static function go(\AcceptanceTester $I)
return $page->goPage('/');
}

public function at($title)
{
$this->tester->see($title);

return $this;
}


public function 新着情報選択($rowNum)
{
$this->tester->click(['css' => "div.ec-newsRole__news > div:nth-child($rowNum) > div.ec-newsRole__newsHeading"]);
Expand Down
44 changes: 43 additions & 1 deletion codeception/acceptance/EA08SysteminfoCest.php
Expand Up @@ -16,6 +16,8 @@
use Page\Admin\LoginHistoryPage;
use Page\Admin\MasterDataManagePage;
use Page\Admin\SystemMemberEditPage;
use Page\Admin\SystemSecurityPage;
use Page\Front\TopPage;

/**
* @group admin
Expand Down Expand Up @@ -608,12 +610,51 @@ public function systeminfo_テンプレート管理_ファイルアップロー
$I->see('この機能は管理者によって制限されています。');
}

public function systeminfo_セキュリティ管理フロントIP制限_許可リスト(AcceptanceTester $I)
{
$I->wantTo('EA0804-UC01-T06 セキュリティ管理 - フロントIP制限(許可リスト)');

// 許可リストに該当するので、閲覧できるパターン
SystemSecurityPage::go($I)->入力_front許可リスト('127.0.0.1')
->登録();
TopPage::go($I)->at('彩のジェラート"CUBE"をご堪能ください');

// 許可リストに該当しないので、閲覧できないパターン
SystemSecurityPage::go($I)->入力_front許可リスト('192.168.100.1')
->登録();
TopPage::go($I)->at('アクセスできません。');

//後片付け(後続処理がエラーにならないため)
SystemSecurityPage::go($I)->入力_front許可リスト('')
->登録();

}

public function systeminfo_セキュリティ管理フロントIP制限_拒否リスト(AcceptanceTester $I)
{
$I->wantTo('EA0804-UC01-T07 セキュリティ管理 - フロントIP制限(拒否リスト)');

// 拒否リストに該当するため閲覧できないパターン
SystemSecurityPage::go($I)->入力_front拒否リスト('127.0.0.1')
->登録();
TopPage::go($I)->at('アクセスできません');

// 拒否リストに該当しないため閲覧可能なパターン
SystemSecurityPage::go($I)->入力_front拒否リスト('192.168.100.1')
->登録();
TopPage::go($I)->at('彩のジェラート"CUBE"をご堪能ください');

//後片付け(後続処理がエラーにならないため)
SystemSecurityPage::go($I)->入力_front拒否リスト('')
->登録();
}

/**
* ATTENTION 後続のテストが失敗するため、最後に実行する必要がある
*/
public function systeminfo_セキュリティ管理IP制限_許可リスト(AcceptanceTester $I)
{
$I->wantTo('EA0804-UC01-T03 セキュリティ管理 - IP制限(許可リスト)');
$I->wantTo('EA0804-UC01-T03 セキュリティ管理 - 管理画面IP制限(許可リスト)');

$findPlugins = Fixtures::get('findPlugins');
$Plugins = $findPlugins();
Expand All @@ -632,4 +673,5 @@ public function systeminfo_セキュリティ管理IP制限_許可リスト(Acce
$I->amOnPage('/'.$config['eccube_admin_route']);
$I->see('アクセスできません。', '//*[@id="error-page"]//h3');
}

}
13 changes: 13 additions & 0 deletions src/Eccube/Controller/Admin/Setting/System/SecurityController.php
Expand Up @@ -61,6 +61,17 @@ public function index(Request $request, CacheUtil $cacheUtil)
$envFile = $this->getParameter('kernel.project_dir').'/.env';
$env = file_get_contents($envFile);

$frontAllowHosts = \json_encode(
array_filter(\explode("\n", StringUtil::convertLineFeed($data['front_allow_hosts'])), function ($str) {
return StringUtil::isNotBlank($str);
})
);
$frontDenyHosts = \json_encode(
array_filter(\explode("\n", StringUtil::convertLineFeed($data['front_deny_hosts'])), function ($str) {
return StringUtil::isNotBlank($str);
})
);

$adminAllowHosts = \json_encode(
array_filter(\explode("\n", StringUtil::convertLineFeed($data['admin_allow_hosts'])), function ($str) {
return StringUtil::isNotBlank($str);
Expand All @@ -73,6 +84,8 @@ public function index(Request $request, CacheUtil $cacheUtil)
);

$env = StringUtil::replaceOrAddEnv($env, [
'ECCUBE_FRONT_ALLOW_HOSTS' => "'{$frontAllowHosts}'",
'ECCUBE_FRONT_DENY_HOSTS' => "'{$frontDenyHosts}'",
'ECCUBE_ADMIN_ALLOW_HOSTS' => "'{$adminAllowHosts}'",
'ECCUBE_ADMIN_DENY_HOSTS' => "'{$adminDenyHosts}'",
'ECCUBE_FORCE_SSL' => $data['force_ssl'] ? '1' : '0',
Expand Down
41 changes: 34 additions & 7 deletions src/Eccube/EventListener/IpAddrListener.php
Expand Up @@ -18,6 +18,7 @@
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpFoundation\IpUtils;

class IpAddrListener implements EventSubscriberInterface
{
Expand All @@ -44,21 +45,47 @@ public function onKernelRequest(RequestEvent $event)
}

if (!$this->requestContext->isAdmin()) {

// 許可リストを取得
$allowFrontHosts = $this->eccubeConfig['eccube_front_allow_hosts'];

foreach ($allowFrontHosts as $host) {
chihiro-adachi marked this conversation as resolved.
Show resolved Hide resolved
// IPアドレス許可リスト範囲になければ拒否
if (!IpUtils::checkIp($event->getRequest()->getClientIp(), $host)) {
throw new AccessDeniedHttpException();
}
}

// 拒否リストを取得
$denyFrontHosts = $this->eccubeConfig['eccube_front_deny_hosts'];

foreach ($denyFrontHosts as $host) {
chihiro-adachi marked this conversation as resolved.
Show resolved Hide resolved
// IPアドレス拒否リスト範囲にあれば拒否
if (IpUtils::checkIp($event->getRequest()->getClientIp(), $host)) {
throw new AccessDeniedHttpException();
}
}

return;
}

// IPアドレス許可リストを確認
$allowHosts = $this->eccubeConfig['eccube_admin_allow_hosts'];
$allowAdminHosts = $this->eccubeConfig['eccube_admin_allow_hosts'];

if (!empty($allowHosts) && array_search($event->getRequest()->getClientIp(), $allowHosts) === false) {
throw new AccessDeniedHttpException();
foreach ($allowAdminHosts as $host) {
chihiro-adachi marked this conversation as resolved.
Show resolved Hide resolved
// IPアドレス許可リスト範囲になければ拒否
if (!IpUtils::checkIp($event->getRequest()->getClientIp(), $host)) {
throw new AccessDeniedHttpException();
}
}

// IPアドレス拒否リストを確認
$denyHosts = $this->eccubeConfig['eccube_admin_deny_hosts'];

if (array_search($event->getRequest()->getClientIp(), $denyHosts) !== false) {
throw new AccessDeniedHttpException();
$denyAdminHosts = $this->eccubeConfig['eccube_admin_deny_hosts'];
foreach ($denyAdminHosts as $host) {
chihiro-adachi marked this conversation as resolved.
Show resolved Hide resolved
// IPアドレス拒否リスト範囲にあれば拒否
if (IpUtils::checkIp($event->getRequest()->getClientIp(), $host)) {
throw new AccessDeniedHttpException();
}
}
}

Expand Down
68 changes: 64 additions & 4 deletions src/Eccube/Form/Type/Admin/SecurityType.php
Expand Up @@ -66,6 +66,12 @@ public function buildForm(FormBuilderInterface $builder, array $options)
$denyHosts = $this->eccubeConfig->get('eccube_admin_deny_hosts');
$denyHosts = implode("\n", $denyHosts);

$allowFrontHosts = $this->eccubeConfig->get('eccube_front_allow_hosts');
$allowFrontHosts = implode("\n", $allowFrontHosts);

$denyFrontHosts = $this->eccubeConfig->get('eccube_front_deny_hosts');
$denyFrontHosts = implode("\n", $denyFrontHosts);

$builder
->add('admin_route_dir', TextType::class, [
'constraints' => [
Expand All @@ -77,6 +83,20 @@ public function buildForm(FormBuilderInterface $builder, array $options)
],
'data' => $this->eccubeConfig->get('eccube_admin_route'),
])
->add('front_allow_hosts', TextareaType::class, [
'required' => false,
'constraints' => [
new Assert\Length(['max' => $this->eccubeConfig['eccube_ltext_len']]),
],
'data' => $allowFrontHosts,
])
->add('front_deny_hosts', TextareaType::class, [
'required' => false,
'constraints' => [
new Assert\Length(['max' => $this->eccubeConfig['eccube_ltext_len']]),
],
'data' => $denyFrontHosts,
])
->add('admin_allow_hosts', TextareaType::class, [
'required' => false,
'constraints' => [
Expand Down Expand Up @@ -110,25 +130,65 @@ public function buildForm(FormBuilderInterface $builder, array $options)
$form = $event->getForm();
$data = $form->getData();

// フロント画面のアクセス許可リストのvalidate
$ips = preg_split("/\R/", $data['front_allow_hosts'], null, PREG_SPLIT_NO_EMPTY);

foreach ($ips as $ip) {
// 適切なIPとビットマスクになっているか
$errors = $this->validator->validate($ip, new Assert\AtLeastOneOf([
'constraints' => [
new Assert\Ip(),
chihiro-adachi marked this conversation as resolved.
Show resolved Hide resolved
new Assert\Cidr()
]
]));
if ($errors->count() > 0) {
$form['front_allow_hosts']->addError(new FormError(trans('admin.setting.system.security.ip_limit_invalid_ip_and_submask', ['%ip%' => $ip])));
}
}

// フロント画面のアクセス拒否リストのvalidate
$ips = preg_split("/\R/", $data['front_deny_hosts'], null, PREG_SPLIT_NO_EMPTY);

foreach ($ips as $ip) {
// 適切なIPとビットマスクになっているか
$errors = $this->validator->validate($ip, new Assert\AtLeastOneOf([
'constraints' => [
new Assert\Ip(),
chihiro-adachi marked this conversation as resolved.
Show resolved Hide resolved
new Assert\Cidr()
]
]));
if ($errors->count() > 0) {
$form['front_deny_hosts']->addError(new FormError(trans('admin.setting.system.security.ip_limit_invalid_ip_and_submask', ['%ip%' => $ip])));
}
}

// 管理画面のアクセス許可リストのvalidate
$ips = preg_split("/\R/", $data['admin_allow_hosts'], null, PREG_SPLIT_NO_EMPTY);

foreach ($ips as $ip) {
$errors = $this->validator->validate($ip, [
// 適切なIPとビットマスクになっているか
$errors = $this->validator->validate($ip, new Assert\AtLeastOneOf([
'constraints' => [
new Assert\Ip(),
new Assert\Cidr()
]
);
]));
if ($errors->count() != 0) {
$form['admin_allow_hosts']->addError(new FormError(trans('admin.setting.system.security.ip_limit_invalid_ipv4', ['%ip%' => $ip])));
}
}

// 管理画面のアクセス拒否リストのvalidate
$ips = preg_split("/\R/", $data['admin_deny_hosts'], null, PREG_SPLIT_NO_EMPTY);

foreach ($ips as $ip) {
$errors = $this->validator->validate($ip, [
// 適切なIPとビットマスクになっているか
$errors = $this->validator->validate($ip, new Assert\AtLeastOneOf([
'constraints' => [
new Assert\Ip(),
new Assert\Cidr()
]
);
]));
if ($errors->count() != 0) {
$form['admin_deny_hosts']->addError(new FormError(trans('admin.setting.system.security.ip_limit_invalid_ipv4', ['%ip%' => $ip])));
}
Expand Down