Verification: a143cc29221c9be0

Php cli max execution time

Php cli max execution time

Установка и настройка системы и необходимых компонентов

Во время установки Ubuntu Server, отметить для установки SSH Server и LAMP. Выбрать автоустановку обновлений безопасности.

# Войти в режим рута
sudo -i
# Задать статический IP:
nano /etc/network/interfaces
auto eth0
iface eth0 inet static
address 192.168.1.7
netmask 255.255.255.0
gateway 192.168.1.1
dns-nameservers 192.168.1.1
dns-search workgroup

Перезагрузиться.

Зайти в систему по SSH и обновить систему целиком:

sudo -i
apt-get update && apt-get upgrade -y && apt-get dist-upgrade -y && apt-get autoremove

Установить все необходимые компоненты и, в данном случае, механизм кэширования APCu + Redis и компоненты для автогенерации favicon:

apt-get install php-zip php-xml php-gd php-json php-curl php-mbstring php-bz2 php-intl php-mcrypt php-apcu redis-server php-redis php-imagick libmagickcore-6.q16-2-extra -y

Если нужен SMB client (для подключения внешних накопителей в Nextcloud), LDAP и Midnight Commander:

apt-get install smbclient php-ldap mc -y

Установка Nextcloud

# Скачать последнюю версию
wget https://download.nextcloud.com/server/releases/latest.tar.bz2
# Распаковать архив в корневую папку веб-сервера
tar xjf latest.tar.bz2 --strip=1 -C /var/www/html
# Удалить исходный архив (если нужно)
rm latest.tar.bz2
# Создать папку для пользовательских данных
mkdir /var/nextcloud-data
# Дать права владельца веб-серверу:
chown -R www-data:www-data /var/www/html /var/nextcloud-data
# Перезапустить Apache:
systemctl restart apache2
# Создать базу MySQL с именем "nextcloud":
mysql -u root -p -e "create database nextcloud";

Открыть браузер, зайти на веб-интерфейс (здесь: 192.168.1.7), задать логин и пароль админа, путь к папке с данными пользователей (здесь: /var/nextcloud-data) и имя БД (здесь: nextcloud). Либо настроить из командной строки:
FIXME - уточнить, можно ли тут обойтись без паролей

sudo -u www-data php /var/www/html/occ maintenance:install --database "mysql" --data-dir "/var/nextcloud-data" --database-name "nextcloud" --database-user "root" --database-pass "password" --admin-user "admin" --admin-pass "password"

Настройка

# Убрать закрывающую строку из конфига и заменить строку overwrite.cli.url на нужную.
# В sed экранирование апострофа безумное - '"'"'
sed -i '
/);/d
/overwrite.cli.url/c \'"'"'overwrite.cli.url\'"'"' => \'"'"'https://192.168.1.7\'"'"',' /var/www/html/config/config.php
 
# Настроить конфиг - "pretty URLs", кэширование, часовой пояс для логов и их ротацию (100 МБ)
echo "'htaccess.RewriteBase' => '/',
'memcache.local' => '\OC\Memcache\APCu',
'memcache.locking' => '\OC\Memcache\Redis',
 'redis' => array(
      'host' => 'localhost',
      'port' => 6379,
       ),
'logtimezone' => 'Europe/Moscow',
'log_rotate_size' => 104857600,
);" >> /var/www/html/config/config.php
 
# Настроить максимальный размер файла на закачку в PHP и лимит памяти
# Проверить версию PHP и путь к используемым php.ini (php --ini), например, он может быть
# /etc/php/7.3/fpm/php.ini. Есть ещё
# /etc/php/7.3/cli/php.ini.
sed -i '
/upload_max_filesize =/c upload_max_filesize = 4G
/post_max_size =/c post_max_size = 4G
/memory_limit =/c memory_limit = 512M' /etc/php/7.3/apache2/php.ini
 
# Настроить параметры opcache
sed -i '
/opcache.enable=/c opcache.enable=1
/opcache.enable_cli=/c opcache.enable_cli=1
/opcache.memory_consumption=/c opcache.memory_consumption=128
/opcache.interned_strings_buffer=/c opcache.interned_strings_buffer=8
/opcache.max_accelerated_files=/c opcache.max_accelerated_files=10000
/opcache.revalidate_freq=/c opcache.revalidate_freq=1
/opcache.save_comments=/c opcache.save_comments=1' /etc/php/7.3/apache2/php.ini

Список часовых поясов для PHP

SSL, mod_env и mod_rewrite для pretty URLs

a2enmod ssl headers env rewrite && a2ensite default-ssl

Включить Strict transport security, Referrer Policy и Forward secrecy:

echo "
 Header always set Strict-Transport-Security "max-age=15768000; includeSubDomains"
 Header always set Referrer-Policy "no-referrer-when-downgrade"

 
# Set Forward Secrecy
SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
SSLHonorCipherOrder on
SSLCipherSuite HIGH:!aNULL:!MD5:!3DES
" >> /etc/apache2/sites-available/default-ssl.conf

Перенаправить HTTP на HTTPS

nano /etc/apache2/sites-available/000-default.conf

VirtualHost *:80>
ServerName www.yourdomain.com
Redirect / https://www.yourdomain.com/
VirtualHost>
systemctl restart apache2

Настроить Pretty URLs

nano /etc/apache2/apache2.conf
 
# В разделе  изменить параметр AllowOverride None на AllowOverride All
# Выйти из редактора.
 
# Обновить файл .htaccess:
sudo -u www-data php /var/www/html/occ maintenance:update:htaccess
 
# Перезапустить Apache:
systemctl restart apache2

Переключить фоновые задачи на выполнение кроном

Выключить ненужные ссылки

config/config.php:

# Выключить ссылку на сброс пароля
'lost_password_link' => 'disabled',
# Remove link “Get your own free account”
'simpleSignUpLink.shown' => false,

Настроить аутентификацию через LDAP

Обновление

# Автоматически:
sudo -u www-data php /var/www/html/updater/updater.phar
 
# Вручную:
# Скачать последний релиз
wget https://download.nextcloud.com/server/releases/latest.tar.bz2
# Распаковать скачанный архив в папку установки
tar xjf latest.tar.bz2 --strip=1 -C /var/www/html
# Дать права владельца веб-серверу:
chown -R www-data:www-data /var/www/html
# Включить режим обслуживания
sudo -u www-data php /var/www/html/occ maintenance:mode --on
# Запустить процесс обновления
sudo -u www-data php /var/www/html/occ upgrade
# Выключить режим обслуживания
sudo -u www-data php /var/www/html/occ maintenance:mode --off

https://docs.nextcloud.com/server/latest/admin_manual/maintenance/update.html

Экспресс-обновление со сменой шлюза

ip route change default via 192.168.1.254 dev eth0
apt update && apt upgrade -y && apt autoremove -y
sudo -u www-data php /var/www/html/updater/updater.phar --no-interaction
ip route change default via 192.168.1.1 dev eth0

Обновление на след. мажорный релиз

# Нужно переключиться на бета-канал обновлений, обновляться, а затем переключиться обратно.
sed -i 's/stable/beta/' /var/www/html/config/config.php
sudo -u www-data php /var/www/html/updater/updater.phar --no-interaction
sed -i 's/beta/stable/' /var/www/html/config/config.php
sudo -u www-data php /var/www/html/updater/updater.phar --no-interaction

Настройка кэширования через сервер Redis

Как-то раз произошла ситуация - невозможно было стереть файл с сервера или обновить его, файл был заблокирован:

file is locked
Error transferring bva.dyndns.info/cloud/remote.php/dav/files/user/123.txt - server replied: Locked («123.txt» is locked)

В соответствующем howto советуют обнулить таблицу блокировок в базе mysql, а чтобы ситуация не повторялась, рекомендуют поставить кэширующий сервис Redis. Так как у меня уже был APCu, было решено поставить Redis для блокировок, а APCu оставить для локального кэша.

В Ubuntu это ставится просто, а в Armbian в репозитории отсутствуют соответствующие пакеты, так что пришлось их собирать из исходников.

Установить Redis

Информация устарела, в репозиториях для процессоров ARM появились собранные пакеты.
Теперь достаточно выполнить команду
apt-get install redis-server php-redis

Сборка из исходников

Настройка Nextcloud и разблокировка файлов

Конфиг Nextcloud в части кэширования нужно привести к следующему виду:

'memcache.local' => '\OC\Memcache\APCu',
'memcache.locking' => '\OC\Memcache\Redis',
 'redis' => array(
      'host' => 'localhost',
      'port' => 6379,
       ),
# Перевести Nextcloud в режим обслуживания:
sudo -u www-data php /var/www/html/occ maintenance:mode --on
# Зайти в базу "cloud" и очистить блокировки:
mysql -u root -p cloud
DELETE FROM oc_file_locks WHERE 1;
quit
# Вывести Nextcloud из режима обслуживания:
sudo -u www-data php /var/www/html/occ maintenance:mode --off
# Перезапустить Apache:
systemctl restart apache2

Дополнительные материалы

Сертификаты

#Сделать папочку для сертификатов
mkdir /etc/ssl/certs/nextcloud
# самоподписанный сертификат на 10 лет без запроса пароля
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout /etc/ssl/certs/nextcloud/nextcloud.key -out /etc/ssl/certs/nextcloud/nextcloud.crt
nano /etc/apache2/sites-available/default-ssl.conf
SSLCertificateKeyFile /etc/ssl/certs/nextcloud/nextcloud.key
SSLCertificateFile /etc/ssl/certs/nextcloud/nextcloud.crt
#SSLCACertificateFile /etc/ssl/certs/nextcloud/nextcloud-int.crt

FIXME

sed -i '
/SSLCertificateKeyFile/c SSLCertificateKeyFile /etc/ssl/certs/nextcloud/nextcloud.key
/SSLCertificateFile/c SSLCertificateFile /etc/ssl/certs/nextcloud/nextcloud.crt' /etc/apache2/sites-available/default-ssl.conf

Импорт контактов из файла vcf

Проблема: выгруженный файл vcf с мобильника на Android 4.4 не загружается в приложение «Контакты» в Nextcloud.

Решение:

  1. Открыть файл в программе tcode (в Windows), чтобы строки с кодировкой Quoted Printable перекодировались в нормальный русский текст. Это можно сделать и из командной строки:

    tcode input.vcf /auto output.vcf
  2. Убрать из всего файла строки ;CHARSET=UTF-8;ENCODING=QUOTED-PRINTABLE

  3. Строки VERSION:2.1 заменить на VERSION:3.0

  4. Сохранить файл в кодировке UTF-8.

Ссылка на выгруженные контакты мобильным приложением

https://path-to-nextcloud-site.com/apps/files/?dir=/.Contacts-Backup

Полезные плагины

Плагин для Outlook

https://download.nextcloud.com/outlook/ Ставить нужно плагин той же разрядности, что и у Офиса (не системы в целом).

Cкачать свежую версию:

# ((Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* |
# ? {$_.Displayname -match 'Microsoft Office' -and $_.InstallLocation -match "\w"} |
# select -expand installlocation -First 1) -split '\\')[1] -match 'x86'
 
$offx64 = gci "$env:ProgramFiles\Microsoft Office\outlook.exe" -Recurse
$offx86 = gci "${env:ProgramFiles(x86)}\Microsoft Office\outlook.exe" -Recurse
$u = "https://download.nextcloud.com/outlook/"
$f = (curl $u).links |? href -like "20*" | select -Last 1 -expand href
$uf = $u + $f
if ($offx64) {
$plugx64 = (curl $uf).links |? href -like "*64*.msi" | select -expand href
$url64 = $uf + $plugx64
curl $url64 -OutFile "$env:userprofile\Downloads\$plugx64"
}
if ($offx86) {
$plugx86 = (curl $uf).links |? href -like "*86*.msi" | select -expand href
$url86 = $uf + $plugx86
curl $url86 -OutFile "$env:userprofile\Downloads\$plugx86"
}
# Путь к файлам перевода:
${env:ProgramFiles(x86)}\Nextcloud Outlook\Resources\Translations

Перевод на русский:

Автоудаление файлов

Снежок

Полезные команды

Включить превью офисных форматов:

nano /var/www/html/config/config.php
'preview_libreoffice_path' => '/usr/bin/libreoffice',

Удалить пользователя username вместе с его каталогом:

u=username
sudo -u www-data php /var/www/html/occ user:delete $u && rm -rf /var/nextcloud-data/$u

Решение проблем

Failed to connect to www.nextcloud.com

В логах куча сообщений:
GuzzleHttp\Exception\ConnectException: cURL error 7: Failed to connect to www.nextcloud.com port 80: Connection timed out

Сайт nexcloud.com реально бывает недоступен. Workaround - отключить проверку на наличие интернета:

echo "'has_internet_connection' => false," >> /var/www/html/config/config.php

Или не обращать внимания.

Some files have not passed the integrity check

После обновления - ошибка подписи файлов:
Some files have not passed the integrity check. Further information on how to resolve this issue can be found in the documentation. (List of invalid files… / Rescan…)

Помимо выполнения рекомендаций, убедиться, что core/signature.json актуальный.

Поломались "красивые" ссылки (без index.php)

Specified key was too long; max key length is 767 bytes

При обновлении Nexcloud ошибка:
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin ENGINE = InnoDB ROW_FORMAT = compressed': SQLSTATE[42000]: Syntax error or access violation: 1071 Specified key was too long; max key length is 767 bytes

Решение:

mysql -u root -p cloud
 
MariaDB [cloud]> set global innodb_large_prefix=on;
MariaDB [cloud]> set global innodb_file_format=Barracuda;
quit
 
sudo -u www-data php /var/www/html/occ maintenance:repair
sudo -u www-data php /var/www/html/occ upgrade

The database is missing some indexes

Проверка в админке пишет: The database is missing some indexes

Решение:

sudo -u www-data php /var/www/html/occ db:add-missing-indices
# В докере:
docker exec -u www-data nc php occ db:add-missing-indices

Обновление прошло неуспешно, PHP грузит систему на 100%, сайт в неотключаемом maintenance mode

Отключить регулярную задачу в crontab.

# Проверить, включен ли apc cli:
php -i | grep apc.enable
  apc.enable_cli => Off => Off
  apc.enabled => On => On
# Если нет, то включить
echo "apc.enable_cli=1" >> /etc/php/7.4/cli/php.ini
# Перейти в каталог NC (обязательно!) и запустить апгрейд заново
cd /var/www/html/cloud
sudo -u www-data php occ upgrade

Включить регулярную задачу в crontab.

https://help.nextcloud.com/t/nextcloud-21-update-needed/108714/25

Your installation has no default phone region set

sudo -u www-data php /var/www/html/occ config:system:set default_phone_region --value="RU"
# Докер
docker exec -u www-data nc php occ config:system:set default_phone_region --value="RU"

Last background job execution ran 15 hours ago. Something seems wrong

Запустить принудительно

sudo -u www-data php -f /var/www/html/cloud/cron.php

PHP Fatal error: Out of memory (allocated 3533701120) (tried to allocate 36864 bytes) in /var/www/html/lib/private/AppFramework/Utility/SimpleContainer.php on line 133

Логика решения

Данное решение идентифицирует неиспользуемые роли IAM в вашем аккаунте. Для этого, вы будете использовать функцию AWS Lambda для проверки всех ролей IAM в вашем аккаунте. Сначала вы определите неиспользуемые роли, основываясь на установленном вами временном окне (число дней с момента последнего использования роли). В данном примере используется 60 дней, но этот параметр настраивается. Затем вы определите статус их соответствия требованиям на основе времени создания и информации о времени последнего использования. Наконец, вы отправите результаты в AWS Config, который сохранит статус соответствия или несоответствия каждой из ролей. Если роль не будет соответствовать требованиям, вы сможете принять меры по исправлению ситуации, например, запретить выполнение всех действий с использованием этой роли.

Необходимые условия

Это решение имеет следующие необходимые условия:

  • Утилита командной строки AWS (AWS CLI) должна быть установлена и настроена на вашем компьютере.
  • AWS Config должен быть включён в вашем аккаунте AWS. Для получения дополнительной информации см. раздел “Начало работы с AWS Config“.

Архитектура решения

Изображение 1: Архитектура решения

Изображение 1: Архитектура решения

Как показано на диаграмме выше, AWS Config (1) проверяет AWS Config Rule с настраиваемой периодичностью (2), что, в свою очередь, запускает функцию Lambda (3). Функция Lambda запрашивает список всех ролей в аккаунте AWS и определяет дату их создания, а также время последнего использования каждой роли, которые предоставляются через GetAccountAuthorizationDetails IAM API (4). После того, как функция Lambda проверит соответствие всех ваших ролей требованиям, она отправляет статус их соответствия требованиям в AWS Config (5). AWS Config сохраняет историю изменений статуса соответствия всех ролей, проанализированных AWS Config Rule, установленным требованиям. Также, если такая опция настроена, уведомления об изменении статуса соответствия требованиям могут быть отправлены в тему (topic) Amazon Simple Notification Service (Amazon SNS). Статус соответствия можно либо посмотреть в консоли управления AWS, либо получить его с помощью AWS CLI или AWS SDK.

Развёртывание решения

Ресурсы для этого решения создаются через шаблон AWS CloudFormation. Прежде чем развернуть решение в вашем аккаунте с помощью шаблона AWS CloudFormation, вам необходимо подготовить исходный код функции Lambda.

Шаг 1: Подготовка к развёртыванию функции Lambda

Для начала, запустите командную строку *nix (Linux, Mac или подсистема Windows для Linux). Выполните приведённые ниже команды, чтобы создать пустую папку с именем iam-role-last-used, в которую вы поместите исходный код Lambda.

mkdir iam-role-last-used
cd iam-role-last-used
touch lambda_function.py

Обратите внимание, что каталог, который вы создали, и код, который будет в нём содержаться, позднее будут сжаты в zip-архив командой AWS CLI cloudformation package. Эта команда также загрузит zip-архив с исходным кодом в ваш бакет S3. Позже, команда cloudformation deploy будет ссылаться на этот бакет S3 при развёртывании шаблона решения.

Теперь создайте слой Lambda с последней версией boto3 SDK. Это гарантирует, что ваша функция Lambda будет использовать последнюю версию boto3 SDK и позволит вам управлять зависимостями при её развёртывании. Это можно сделать, следуя шагам с 1 по 4 в этой инструкции. Обязательно запишите ARN-идентификатор созданного вами слоя Lambda, поскольку он вам понадобится позже.

Наконец, откройте файл lambda_function.py в текстовом редакторе или интегрированной среде разработки (IDE) и скопируйте следующий исходный код в файл lambda_function.py:

import boto3
from botocore.exceptions import ClientError
from botocore.config import Config
import datetime
import fnmatch
import json
import os
import re
import logging


logger = logging.getLogger()
logging.basicConfig(
    format="[%(asctime)s] %(levelname)s [%(module)s.%(funcName)s:%(lineno)d] %(message)s", datefmt="%H:%M:%S"
)
logger.setLevel(os.getenv('log_level', logging.INFO))

# Configure boto retries
BOTO_CONFIG = Config(retries=dict(max_attempts=5))

# Define the default resource to report to Config Rules
DEFAULT_RESOURCE_TYPE = 'AWS::IAM::Role'

CONFIG_ROLE_TIMEOUT_SECONDS = 900

# Set to True to get the lambda to assume the Role attached on the Config service (useful for cross-account).
ASSUME_ROLE_MODE = False

# Evaluation strings for Config evaluations
COMPLIANT = 'COMPLIANT'
NON_COMPLIANT = 'NON_COMPLIANT'


# This gets the client after assuming the Config service role either in the same AWS account or cross-account.
def get_client(service, execution_role_arn):
    if not ASSUME_ROLE_MODE:
        return boto3.client(service)
    credentials = get_assume_role_credentials(execution_role_arn)
    return boto3.client(service, aws_access_key_id=credentials['AccessKeyId'],
                        aws_secret_access_key=credentials['SecretAccessKey'],
                        aws_session_token=credentials['SessionToken'],
                        config=BOTO_CONFIG
                        )


def get_assume_role_credentials(execution_role_arn):
    sts_client = boto3.client('sts')
    try:
        assume_role_response = sts_client.assume_role(RoleArn=execution_role_arn,
                                                      RoleSessionName="configLambdaExecution",
                                                      DurationSeconds=CONFIG_ROLE_TIMEOUT_SECONDS)
        return assume_role_response['Credentials']
    except ClientError as ex:
        if 'AccessDenied' in ex.response['Error']['Code']:
            ex.response['Error']['Message'] = "AWS Config does not have permission to assume the IAM role."
        else:
            ex.response['Error']['Message'] = "InternalError"
            ex.response['Error']['Code'] = "InternalError"
        raise ex


# Validates role pathname whitelist as passed via AWS Config parameters and returns a list of comma separated patterns.
def validate_whitelist(unvalidated_role_pattern_whitelist):
    # Names of users, groups, roles must be alphanumeric, including the following common
    # characters: plus (+), equal (=), comma (,), period (.), at (@), underscore (_), and hyphen (-).

    if not unvalidated_role_pattern_whitelist:
        return None

    regex = re.compile('^[-a-zA-Z0-9+=,.@_/|*]+')
    if regex.search(unvalidated_role_pattern_whitelist):
        raise ValueError("[Error] Provided whitelist has invalid characters")

    return unvalidated_role_pattern_whitelist.split('|')


# This uses Unix filename pattern matching (as opposed to regular expressions), as documented here:
# https://docs.python.org/3.7/library/fnmatch.html.  Please note that if using a wildcard, e.g. "*", you should use
# it sparingly/appropriately.
# If the rolename matches the pattern, then it is whitelisted
def is_whitelisted_role(role_pathname, pattern_list):
    if not pattern_list:
        return False

    # If role_pathname matches pattern, then return True, else False
    # eg. /service-role/aws-codestar-service-role matches pattern /service-role/*
    # https://docs.python.org/3.7/library/fnmatch.html
    for pattern in pattern_list:
        if fnmatch.fnmatch(role_pathname, pattern):
            # whitelisted
            return True

    # not whitelisted
    return False


# Form an evaluation as a dictionary. Suited to report on scheduled rules.  More info here:
#   https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/config.html#ConfigService.Client.put_evaluations
def build_evaluation(resource_id, compliance_type, notification_creation_time, resource_type=DEFAULT_RESOURCE_TYPE, annotation=None):
    evaluation = {}
    if annotation:
        evaluation['Annotation'] = annotation
    evaluation['ComplianceResourceType'] = resource_type
    evaluation['ComplianceResourceId'] = resource_id
    evaluation['ComplianceType'] = compliance_type
    evaluation['OrderingTimestamp'] = notification_creation_time
    return evaluation


# Determine if any roles were used to make an AWS request
def determine_last_used(role_name, role_last_used, max_age_in_days, notification_creation_time):

    last_used_date = role_last_used.get('LastUsedDate', None)
    used_region = role_last_used.get('Region', None)

    if not last_used_date:
        compliance_result = NON_COMPLIANT
        reason = "No record of usage"
        logger.info(f"NON_COMPLIANT: {role_name} has never been used")
        return build_evaluation(role_name, compliance_result, notification_creation_time, resource_type=DEFAULT_RESOURCE_TYPE, annotation=reason)


    days_unused = (datetime.datetime.now() - last_used_date.replace(tzinfo=None)).days

    if days_unused > max_age_in_days:
        compliance_result = NON_COMPLIANT
        reason = f"Was used {days_unused} days ago in {used_region}"
        logger.info(f"NON_COMPLIANT: {role_name} has not been used for {days_unused} days, last use in {used_region}")
        return build_evaluation(role_name, compliance_result, notification_creation_time, resource_type=DEFAULT_RESOURCE_TYPE, annotation=reason)

    compliance_result = COMPLIANT
    reason = f"Was used {days_unused} days ago in {used_region}"
    logger.info(f"COMPLIANT: {role_name} used {days_unused} days ago in {used_region}")
    return build_evaluation(role_name, compliance_result, notification_creation_time, resource_type=DEFAULT_RESOURCE_TYPE, annotation=reason)


# Returns a list of docts, each of which has authorization details of each role.  More info here:
#   https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/iam.html#IAM.Client.get_account_authorization_details
def get_role_authorization_details(iam_client):

    roles_authorization_details = []
    roles_list = iam_client.get_account_authorization_details(Filter=['Role'])

    while True:
        roles_authorization_details += roles_list['RoleDetailList']
        if 'Marker' in roles_list:
            roles_list = iam_client.get_account_authorization_details(Filter=['Role'], MaxItems=100, Marker=roles_list['Marker'])
        else:
            break

    return roles_authorization_details


# Check the compliance of each role by determining if role last used is > than max_days_for_last_used
def evaluate_compliance(event, context):

    # Initialize our AWS clients
    iam_client = get_client('iam', event["executionRoleArn"])
    config_client = get_client('config', event["executionRoleArn"])

    # List of resource evaluations to return back to AWS Config
    evaluations = []

    # List of dicts of each role's authorization details as returned by boto3
    all_roles = get_role_authorization_details(iam_client)

    # Timestamp of when AWS Config triggered this evaluation
    notification_creation_time = str(json.loads(event['invokingEvent'])['notificationCreationTime'])

    # ruleParameters is received from AWS Config's user-defined parameters
    rule_parameters = json.loads(event["ruleParameters"])

    # Maximum allowed days that a role can be unused, or has been last used for an AWS request
    max_days_for_last_used = int(os.environ.get('max_days_for_last_used', '60'))
    if 'max_days_for_last_used' in rule_parameters:
        max_days_for_last_used = int(rule_parameters['max_days_for_last_used'])

    whitelisted_role_pattern_list = []
    if 'role_whitelist' in rule_parameters:
        whitelisted_role_pattern_list = validate_whitelist(rule_parameters['role_whitelist'])

    # Iterate over all our roles.  If the creation date of a role is 

Рассмотрим, как работает вышеупомянутый код. AWS Config Rule запускает функцию Lambda, вызывая функцию evaluate_compliance(). Функция evaluate_compliance() делает следующее:

  1. Запрашивает информацию о всех ролях IAM с помощью GetAccountAuthorizationDetails API, как уже упоминалось ранее. Эта информация включает в себя дату создания каждой роли, а также время последнего её использования.
  2. Помечает роль как соответствующую требованиям, если имя роли соответствует одному из шаблонов из списка whitelisted_role_pattern_list. Этот список шаблонов передается в Config Rule через параметр конфигурации шаблона CloudFormation с именем RolePatternWhitelist. В разделе “Игнорируемые роли” ниже приведены инструкции о том, как это сделать.
  3. Помечает роль как соответствующую требованиям, если её возраст в днях (role_age_in_days) меньше или равен параметру MaxDaysForLastUsed (max_days_for_last_used). Это значение также передаётся через параметр конфигурации шаблона CloudFormation. Этот параметр используется для конфигурации временного интервала, в течение которого роль может быть неактивна.
  4. Если ни одно из вышеперечисленных условий не выполняется, то вызывается функция determine_last_used(), и роль помечается как несоответствующая требованиям, если значение параметра days_unused больше, чем значение параметра max_age_in_days.
  5. В конце, функция evaluate_compliance() вызывает функцию put_evaluations(), чтобы сохранить статус соответствия ролей требованиям в AWS Config.

Шаг 2: Развёртывание шаблона AWS CloudFormation

Далее, создайте файл для шаблона CloudFormation с названием iam-role-last-used.yml. Этот шаблон использует AWS Serverless Application Model (AWS SAM), которая является расширением CloudFormation. AWS SAM упрощает развёртывание и избавляет вас от необходимости вручную загружать zip-архив с исходным кодом функции Lambda в бакет Amazon S3. Чтобы убедиться, что при развёртывании шаблона zip-архив с исходным кодом будет найден, поместите этот файл рядом с созданным вами ранее каталогом iam-role-last-used. Затем скопируйте код, приведённый ниже, и сохраните его в файл iam-role-last-used.yml.

AWSTemplateFormatVersion: '2010-09-09'
Description: "Creates an AWS Config rule and Lambda to check all roles' last used compliance"
Transform: 'AWS::Serverless-2016-10-31'
Parameters:

  MaxDaysForLastUsed:
    Description: Checks the number of days allowed for a role to not be used before being non-compliant
    Type: Number
    Default: 60
    MaxValue: 365

  NameOfSolution:
    Type: String
    Default: iam-role-last-used
    Description: The name of the solution - used for naming of created resources

  RolePatternWhitelist:
    Description: Pipe separated whitelist of role pathnames using simple pathname matching
    Type: String
    Default: ''
    AllowedPattern: '[-a-zA-Z0-9+=,.@_/|*]+|^$'

  LambdaLayerArn:
    Type: String
    Description: The ARN for the Lambda Layer you will use.
  
Resources:
  LambdaInvokePermission:
    Type: 'AWS::Lambda::Permission'
    DependsOn: CheckRoleLastUsedLambda
    Properties: 
      FunctionName: !GetAtt CheckRoleLastUsedLambda.Arn
      Action: lambda:InvokeFunction
      Principal: config.amazonaws.com
      SourceAccount: !Ref 'AWS::AccountId'

  LambdaExecutionRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: !Sub '${NameOfSolution}-${AWS::Region}'
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service: lambda.amazonaws.com
          Action:
          - sts:AssumeRole
      Path: /
      Policies:
      - PolicyName: !Sub '${NameOfSolution}'
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
            - config:PutEvaluations
            Resource: '*'
          - Effect: Allow
            Action:
            - iam:GetAccountAuthorizationDetails
            Resource: '*'
          - Effect: Allow
            Action:
            - logs:CreateLogStream
            - logs:PutLogEvents
            Resource:
            - !Sub 'arn:${AWS::Partition}:logs:${AWS::Region}:*:log-group:/aws/lambda/${NameOfSolution}:log-stream:*'

  CheckRoleLastUsedLambda:
    Type: 'AWS::Serverless::Function'
    Properties:
      Description: "Checks IAM roles' last used info for AWS Config"
      FunctionName: !Sub '${NameOfSolution}'
      Handler: lambda_function.evaluate_compliance
      MemorySize: 256
      Role: !GetAtt LambdaExecutionRole.Arn
      Runtime: python3.7
      Timeout: 300
      CodeUri: ./iam-role-last-used
      Layers:
      - !Ref LambdaLayerArn

  LambdaLogGroup:
    Type: 'AWS::Logs::LogGroup'
    Properties: 
      LogGroupName: !Sub '/aws/lambda/${NameOfSolution}'
      RetentionInDays: 30

  ConfigCustomRule:
    Type: 'AWS::Config::ConfigRule'
    DependsOn:
    - LambdaInvokePermission
    - LambdaExecutionRole
    Properties:
      ConfigRuleName: !Sub '${NameOfSolution}'
      Description: Checks the number of days that an IAM role has not been used to make a service request. If the number of days exceeds the specified threshold, it is marked as non-compliant.
      InputParameters: !Sub '{"role-whitelist":"${RolePatternWhitelist}","max_days_for_last_used":"${MaxDaysForLastUsed}"}'
      Source: 
        Owner: CUSTOM_LAMBDA
        SourceDetails: 
        - EventSource: aws.config
          MaximumExecutionFrequency: TwentyFour_Hours
          MessageType: ScheduledNotification
        SourceIdentifier: !GetAtt CheckRoleLastUsedLambda.Arn

Ниже приведена краткая информация о данном шаблоне CloudFormation.

  • Параметры (эти переменные можно переопределять):
    • MaxDaysForLastUsed – максимальное количество дней, в течение которого роль может быть неактивна прежде, чем она получит статус несоответствующей требованиям.
    • NameOfSolution – название решения: оно используется для именования создаваемых ресурсов.
    • RolePatternWhitelist – список ролей, перечисленных через разделитель (“|”), которые всегда помечаются как соответствующие требованиям (см. раздел “Игнорируемые роли” ниже).
    • LambdaLayerArn – ARN-идентификатор слоя Lambda, созданного вами ранее на шаге 1.
  • Ресурсы (эти ресурсы будут созданы в вашем аккаунте AWS):
    • LambdaInvokePermission – разрешение для AWS Config запускать вашу функцию Lambda.
    • LambdaExecutionRole – роль IAM, которая будет использоваться функцией Lambda. Эта роль включает в себя политики с разрешениями производить следующие действия: iam:GetAccountAuthorizationDetails, config:PutEvaluations, logs:CreateLogStream, и logs:PutLogEvents. Действие PutEvaluations позволяет функции сохранять в AWS Config статус соответствия ресурсов требованиям. Действия CreateLogStream и PutLogEvents позволяют сохранять логи вашей функции Lambda в AWS CloudWatch Logs.
    • CheckRoleLastUsedLambda – описывает вашу функцию Lambda и её параметры.
    • LambdaLogGroup – группа CloudWatch Logs, где будут сохранены логи вашей функции Lambda.
    • ConfigCustomRule – описывает AWS Config Rule и его параметры.

Используйте команду AWS CLI cloudformation package с шаблоном CloudFormation, который вы сохранили ранее, чтобы создать zip-архив, содержащий исходный код вашей функции Lambda и загрузить его в указанный вами бакет S3, как показано ниже. Обязательно замените на регион, в котором вы развёртываете решение и на имя бакета, не включая префикс s3://:

aws cloudformation package --region  --template-file iam-role-last-used.yml \
--s3-bucket  \
--output-template-file iam-role-last-used-transformed.yml

В результате будет создан файл iam-role-last-used-transformed.yml, содержащий ссылку на бакет и путь к zip-архиву, содержащему исходный код, необходимые CloudFormation для развёртывания функции Lambda.

Теперь, разверните решение в вашем аккаунте AWS с помощью команды cloudformation deploy, приведённой ниже. Вы можете задать другие значения для NameOfSolutionMaxDaysForLastAccess, или RolePatternWhitelist, используя опцию --parameter-overrides. В противном случае будут использоваться значения по умолчанию, которые указаны в верхней части шаблона CloudFormation, в разделе Parameters. Не забудьте заменить на регион, в котором вы развёртываете решение, и на ARN-идентификатор слоя Lambda, созданного вами ранее на шаге 1.

aws cloudformation deploy --region  --template-file iam-role-last-used-transformed.yml \
--stack-name iam-role-last-used \
--parameter-overrides NameOfSolution='iam-role-last-used' \
MaxDaysForLastUsed=60 \
RolePatternWhitelist='/breakglass-role|/security-*' \
LambdaLayerArn='' \
--capabilities CAPABILITY_NAMED_IAM

Развёртывание решения обычно занимает всего несколько минут и заканчивается сообщением AWS CLI об успешном завершении команды:

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - iam-role-last-used

Шаг 3: Просмотр результатов

Теперь, когда развёртывание завершено, вы можете просмотреть результаты проверки соответствия требованиям, перейдя в консоль AWS Config.

  1. Выберите тот же регион, в котором был развёрнут шаблон CloudFormation.
  2. Выберите Rules в левой панели, в результате чего откроется текущий список Config Rules в вашем аккаунте AWS.
  3. Выберите Config Rule iam-role-last-used для просмотра детальной информации, как показано на изображении 2.

Если в поле Overall rule status отображается время последнего запуска проверки, первоначальная проверка соответствия завершена. Для успешного завершения проверки может потребоваться подождать несколько минут, если результаты ещё недоступны. Вы можете несколько раз обновить консоль в веб-браузере, чтобы проверить, завершена ли проверка.

Изображение 2: Детальная информация о AWS Config Rule

Изображение 2: Детальная информация о AWS Config Rule

После завершения проверки ваших ролей на соответствие требованиям вы сможете просмотреть результаты на этой же странице. На скриншоте ниже видно, что есть несколько несоответствующих требованиям ролей. Вы можете переключаться между просмотром соответствующих и несоответствующих требованиям ролей с помощью выпадающего меню в разделе Compliance status.

Изображение 3: Просмотр статуса соответствия требованиям

Изображение 3: Просмотр статуса соответствия требованиям

Вы можете навести курсор на символ “i”, чтобы увидеть причину, по который роль была помечена как несоответствующая требованиям (см. изображение 4).

Изображение 4: Дополнительная информация о статусе роли

Изображение 4: Дополнительная информация о статусе роли

Шаг 4: Экспорт отчёта о соответствии требованиям

Возможно, после успешного завершения проверки, вы захотите создать экспортируемый отчёт о соответствии требованиям. Вы можете использовать AWS CLI для написания скриптов и автоматического создания отчётов для ваших команд разработки, или команды безопасности. Они могут использовать эти отчёты для дальнейшей проверки несоответствующих требованиям ролей и принятия мер, если та или иная роль больше не нужна. Команда AWS CLI, представленная ниже, демонстрирует, как можно это сделать. Обратите внимание, что команда, приведённая ниже, состоит из одной строки:

aws configservice get-compliance-details-by-config-rule --config-rule-name iam-role-last-used --output text --query 'EvaluationResults [*].{A:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,B:ComplianceType,C:Annotation}'

Результат будет аналогичен приведённым ниже строкам, колонки будут отделены друг от друга отступами. В первой колонке выводится название роли. Во второй колонке указывается статус соответствия. Последняя колонка объясняет причину статуса соответствия или несоответствия:

AdminRole   COMPLIANT      Was last used in us-west-2 46 days ago
Ec2DevRole  NON_COMPLIANT  No record of usage

Исправление несоответствующих требованиям ролей

Теперь, когда у вас есть отчет о несоответствующих требованиям ролях, вы должны решить, что с ними делать. Если ваши команды согласны с тем, что роль не является необходимой, исправление может заключаться в простом удалении роли. Если вы не уверены, вы можете сохранить роль, но запретить выполнение всех действий. Вы можете сделать это, прикрепив новую политику, запрещающую все действия для всех ресурсов. Повторное включение роли будет очень простым, достаточно будет удалить добавленную политику. В противном случае, если роль необходима, но не часто используется, вы можете внести её в список игнорируемых ролей с помощью метода, описанного ниже. 

Игнорируемые роли

Игнорируемые роли будут помечены как соответствующие требованиям, даже если они не используются. У вас могут быть такие роли, как роль для реагирования на инциденты безопасности или для других экстренных ситуаций, которые требуют внесения в список разрешённых ролей.

Список игнорируемых ролей настраивается параметром RolePatternWhitelist шаблона CloudFormation и хранится как параметр AWS Config Rule. Синтаксис использует шаблон соответствия имен файлов UNIX. Если необходимо указать несколько шаблонов для названия ролей, используйте символ “|” в качестве разделителя между ними. При этом, каждый шаблон будет сопоставлен с именем роли, включая путь. Например, если необходимо внести в список игнорируемых ролей роли breakglass-role, security-incident-response-role и security-audit-role, то параметр RolePatternWhitelist шаблона CloudFormation будет выглядеть следующим образом:

/breakglass-role|/security-*

Важно: Используйте звёздочки (*) аккуратно, так как они будут соответствовать любому количеству любых символов в названии роли! 

Дополнительная функциональность

В этой статье я постарался сохранить архитектуру и код простыми, чтобы сделать решение более лёгким для использования. Однако вы можете дополнительно настроить решение с помощью следующей функциональности:

  • Решение можно развернуть в качестве AWS Config Rule, которое централизованно развёртывается через AWS Organizations для проверки всех аккаунтов AWS, входящих в вашу AWS Organization. Единственное, что требуется изменить, это установить ASSUME_ROLE_MODE = True в предоставленном исходном коде. Также необходимо внести изменения в разрешение для AWS Config запускать функцию Lambda, которые позволят другим аккаунтам из вашей AWS Organization напрямую вызывать эту функцию Lambda.
  • Ручное или автоматическое исправление несоответствующих ролей может быть выполнено через AWS Config путём настройки пользовательского исправления. AWS Config применяет исправление с помощью документов автоматизации AWS Systems Manager Automation.

Выводы

В этой статье я показал вам, как использовать AWS IAM и AWS Config для реализации непрерывного мониторинга безопасности, который обеспечивает прозрачность ваших ролей IAM и времени их последнего использования. Я также показал, как просматривать результаты проверки соответствия ролей IAM требованиям в консоли управления AWS и экспортировать результаты с помощью AWS CLI. Наконец, я предложил различные опции исправления и возможность составления списка разрешённых ролей, которые необходимы, но при этом редко используются. Эти методы могут помочь вам улучшить методы обеспечения безопасности и соответствия требованиям, предотвращая непреднамеренный доступ через роли IAM.