Change secrets from being stored in flat files to being stored in PHP INI configuration

This commit is contained in:
Alex Cabal 2022-07-02 13:36:46 -05:00
parent 9d36a7c013
commit 0875e697b4
10 changed files with 24 additions and 32 deletions

1
.gitignore vendored
View file

@ -13,3 +13,4 @@ composer.lock
*.log *.log
www/manual/* www/manual/*
!www/manual/index.php !www/manual/index.php
config/php/fpm/standardebooks.org-secrets.ini

View file

@ -9,10 +9,12 @@ max_execution_time = 120
allow_url_fopen = false allow_url_fopen = false
allow_url_include = false allow_url_include = false
expose_php = off expose_php = off
site_status = "live"
[Date] [Date]
date.timezone = Etc/UTC date.timezone = Etc/UTC
[opcache] [opcache]
opcache.validate_timestamps = off opcache.validate_timestamps = off
[se]
se.site_status = "live"

View file

@ -9,10 +9,12 @@ max_execution_time = 120
allow_url_fopen = false allow_url_fopen = false
allow_url_include = false allow_url_include = false
expose_php = off expose_php = off
site_status = "live"
[Date] [Date]
date.timezone = Etc/UTC date.timezone = Etc/UTC
[opcache] [opcache]
opcache.validate_timestamps = off opcache.validate_timestamps = off
[se]
se.site_status = "dev"

View file

@ -16,9 +16,6 @@ parameters:
- '#Method Ebook::NullIfEmpty\(\) has parameter \$elements with no type specified.#' - '#Method Ebook::NullIfEmpty\(\) has parameter \$elements with no type specified.#'
- '#Method HttpInput::GetHttpVar\(\) has no return type specified.#' - '#Method HttpInput::GetHttpVar\(\) has no return type specified.#'
- '#Method HttpInput::GetHttpVar\(\) has parameter \$default with no type specified.#' - '#Method HttpInput::GetHttpVar\(\) has parameter \$default with no type specified.#'
# Ignore symbols that PHPStan can't find
- '#Constant EMAIL_SMTP_USERNAME not found.#'
level: level:
8 8
paths: paths:

View file

@ -7,7 +7,7 @@ use function Safe\strtotime;
const SITE_STATUS_LIVE = 'live'; const SITE_STATUS_LIVE = 'live';
const SITE_STATUS_DEV = 'dev'; const SITE_STATUS_DEV = 'dev';
define('SITE_STATUS', get_cfg_var('site_status') ?: SITE_STATUS_DEV); // Set in the PHP INI configuration for both CLI and FPM. Have to use define() and not const so we can use a function. define('SITE_STATUS', get_cfg_var('se.site_status') ?: SITE_STATUS_DEV); // Set in the PHP INI configuration for both CLI and FPM. Have to use define() and not const so we can use a function.
// No trailing slash on any of the below constants. // No trailing slash on any of the below constants.
if(SITE_STATUS == SITE_STATUS_LIVE){ if(SITE_STATUS == SITE_STATUS_LIVE){
@ -35,14 +35,10 @@ const SORT_LENGTH = 'length';
const CAPTCHA_IMAGE_HEIGHT = 72; const CAPTCHA_IMAGE_HEIGHT = 72;
const CAPTCHA_IMAGE_WIDTH = 230; const CAPTCHA_IMAGE_WIDTH = 230;
const NO_REPLY_EMAIL_ADDRESS = 'admin@standardebooks.org'; const NO_REPLY_EMAIL_ADDRESS = 'admin@standardebooks.org';
const ADMIN_EMAIL_ADDRESS = 'admin@standardebooks.org'; const ADMIN_EMAIL_ADDRESS = 'admin@standardebooks.org';
const EDITOR_IN_CHIEF_EMAIL_ADDRESS = 'alex@standardebooks.org'; const EDITOR_IN_CHIEF_EMAIL_ADDRESS = 'alex@standardebooks.org';
// We don't define the email username/password in this file to define('EMAIL_SMTP_USERNAME', get_cfg_var('se.secrets.postmark.username'));
// 1) avoid a filesystem read when email isn't being used, and
// 2) allow scripts run by users not in the www-data group to succeed, otherwise they will not be able to open secret files on startup and crash
const POSTMARK_SECRET_FILE_PATH = SITE_ROOT . '/config/secrets/postmarkapp.com';
const EMAIL_SMTP_HOST = 'smtp-broadcasts.postmarkapp.com'; const EMAIL_SMTP_HOST = 'smtp-broadcasts.postmarkapp.com';
const EMAIL_POSTMARK_STREAM_BROADCAST = 'the-standard-ebooks-newsletter'; const EMAIL_POSTMARK_STREAM_BROADCAST = 'the-standard-ebooks-newsletter';
@ -87,6 +83,13 @@ const FA_FEE_PERCENT = 0.87;
const SE_SUBJECTS = ['Adventure', 'Autobiography', 'Biography', 'Childrens', 'Comedy', 'Drama', 'Fantasy', 'Fiction', 'Horror', 'Memoir', 'Mystery', 'Nonfiction', 'Philosophy', 'Poetry', 'Satire', 'Science Fiction', 'Shorts', 'Spirituality', 'Tragedy', 'Travel']; const SE_SUBJECTS = ['Adventure', 'Autobiography', 'Biography', 'Childrens', 'Comedy', 'Drama', 'Fantasy', 'Fiction', 'Horror', 'Memoir', 'Mystery', 'Nonfiction', 'Philosophy', 'Poetry', 'Satire', 'Science Fiction', 'Shorts', 'Spirituality', 'Tragedy', 'Travel'];
const GITHUB_IGNORED_REPOS = ['tools', 'manual', 'web']; // If we get GitHub push requests featuring these repos, silently ignore instead of returning an error.
const GITHUB_WEBHOOK_LOG_FILE_PATH = '/var/log/local/webhooks-github.log'; // Must be writable by `www-data` Unix user.
const POSTMARK_WEBHOOK_LOG_FILE_PATH = '/var/log/local/webhooks-postmark.log'; // Must be writable by `www-data` Unix user.
const ZOHO_WEBHOOK_LOG_FILE_PATH = '/var/log/local/webhooks-zoho.log'; // Must be writable by `www-data` Unix user.
const DONATIONS_LOG_FILE_PATH = '/var/log/local/donations.log'; // Must be writable by `www-data` Unix user.
define('PD_YEAR', intval(gmdate('Y')) - 96); define('PD_YEAR', intval(gmdate('Y')) - 96);
define('PD_STRING', 'January 1, ' . (PD_YEAR + 1)); define('PD_STRING', 'January 1, ' . (PD_YEAR + 1));
@ -95,15 +98,3 @@ define('DONATION_ALERT_ON', DONATION_HOLIDAY_ALERT_ON || rand(1, 4) == 2);
define('DONATION_DRIVE_ON', false); define('DONATION_DRIVE_ON', false);
define('DONATION_DRIVE_COUNTER_ON', false); define('DONATION_DRIVE_COUNTER_ON', false);
const GITHUB_SECRET_FILE_PATH = SITE_ROOT . '/config/secrets/se-vcs-bot@github.com'; // Set in the GitHub organization global webhook settings.
const GITHUB_WEBHOOK_LOG_FILE_PATH = '/var/log/local/webhooks-github.log'; // Must be writable by `www-data` Unix user.
const GITHUB_IGNORED_REPOS = ['tools', 'manual', 'web']; // If we get GitHub push requests featuring these repos, silently ignore instead of returning an error.
const POSTMARK_WEBHOOK_LOG_FILE_PATH = '/var/log/local/webhooks-postmark.log'; // Must be writable by `www-data` Unix user.
const ZOHO_SECRET_FILE_PATH = SITE_ROOT . '/config/secrets/webhooks@zoho.com'; // Set in the GitHub organization global webhook settings.
const ZOHO_WEBHOOK_LOG_FILE_PATH = '/var/log/local/webhooks-zoho.log'; // Must be writable by `www-data` Unix user.
const FA_SECRET_FILE_PATH = SITE_ROOT . '/config/secrets/fracturedatlas.org';
const DONATIONS_LOG_FILE_PATH = '/var/log/local/donations.log'; // Must be writable by `www-data` Unix user.

View file

@ -5,8 +5,6 @@ use PHPMailer\PHPMailer\Exception;
use function Safe\define; use function Safe\define;
use function Safe\file_get_contents; use function Safe\file_get_contents;
define('EMAIL_SMTP_USERNAME', trim(file_get_contents(POSTMARK_SECRET_FILE_PATH)));
class Email{ class Email{
public $To = ''; public $To = '';
public $From = ''; public $From = '';

View file

@ -34,7 +34,8 @@ $driver = null;
$log = new Log(DONATIONS_LOG_FILE_PATH); $log = new Log(DONATIONS_LOG_FILE_PATH);
$lastMonth = (new DateTime())->sub(new DateInterval('P45D')); // 45 days, a 15 day grace period before Patrons Circle members are dropped off $lastMonth = (new DateTime())->sub(new DateInterval('P45D')); // 45 days, a 15 day grace period before Patrons Circle members are dropped off
$lastYear = (new DateTime())->sub(new DateInterval('P1Y')); $lastYear = (new DateTime())->sub(new DateInterval('P1Y'));
$faCredentials = explode("\n", trim(file_get_contents(FA_SECRET_FILE_PATH))); $faUsername = get_cfg_var('se.secrets.fractured_atlas.username');
$faPassword = get_cfg_var('se.secrets.fractured_atlas.password');
// Test donations // Test donations
// fa000cbf-af6f-4c14-8919-da6cf81a27ea Regular donation, patrons, public, recurring // fa000cbf-af6f-4c14-8919-da6cf81a27ea Regular donation, patrons, public, recurring
@ -88,8 +89,8 @@ try{
$submitButton = $driver->wait(20, 250)->until(WebDriverExpectedCondition::visibilityOfElementLocated(WebDriverBy::xpath('//button[@type="submit"]'))); $submitButton = $driver->wait(20, 250)->until(WebDriverExpectedCondition::visibilityOfElementLocated(WebDriverBy::xpath('//button[@type="submit"]')));
// Fill out and submit the form // Fill out and submit the form
$emailField->sendKeys($faCredentials[0]); $emailField->sendKeys($faUsername);
$passwordField->sendKeys($faCredentials[1]); $passwordField->sendKeys($faPassword);
$submitButton->click(); $submitButton->click();
} }

View file

@ -28,7 +28,7 @@ try{
$hashAlgorithm = $splitHash[0]; $hashAlgorithm = $splitHash[0];
$hash = $splitHash[1]; $hash = $splitHash[1];
if(!hash_equals($hash, hash_hmac($hashAlgorithm, $post, preg_replace("/[\r\n]/ius", '', file_get_contents(GITHUB_SECRET_FILE_PATH))))){ if(!hash_equals($hash, hash_hmac($hashAlgorithm, $post, get_cfg_var('se.secrets.github.se_vcs_bot.secret')))){
throw new Exceptions\InvalidCredentialsException(); throw new Exceptions\InvalidCredentialsException();
} }

View file

@ -11,7 +11,7 @@ use function Safe\substr;
$log = new Log(POSTMARK_WEBHOOK_LOG_FILE_PATH); $log = new Log(POSTMARK_WEBHOOK_LOG_FILE_PATH);
try{ try{
$smtpUsername = trim(file_get_contents(POSTMARK_SECRET_FILE_PATH)); $smtpUsername = get_cfg_var('se.secrets.postmark.username');
$log->Write('Received Postmark webhook.'); $log->Write('Received Postmark webhook.');
@ -19,7 +19,7 @@ try{
throw new Exceptions\WebhookException('Expected HTTP POST.'); throw new Exceptions\WebhookException('Expected HTTP POST.');
} }
$apiKey = trim(file_get_contents(SITE_ROOT . '/config/secrets/webhooks@postmarkapp.com')); $apiKey = get_cfg_var('se.secrets.postmark.api_key');
// Ensure this webhook actually came from Postmark // Ensure this webhook actually came from Postmark
if($apiKey != ($_SERVER['HTTP_X_SE_KEY'] ?? '')){ if($apiKey != ($_SERVER['HTTP_X_SE_KEY'] ?? '')){

View file

@ -23,7 +23,7 @@ try{
$post = file_get_contents('php://input'); $post = file_get_contents('php://input');
// Validate the Zoho secret. // Validate the Zoho secret.
if(!hash_equals($_SERVER['HTTP_X_HOOK_SIGNATURE'], base64_encode(hash_hmac('sha256', $post, preg_replace("/[\r\n]/ius", '', file_get_contents(ZOHO_SECRET_FILE_PATH)), true)))){ if(!hash_equals($_SERVER['HTTP_X_HOOK_SIGNATURE'], base64_encode(hash_hmac('sha256', $post, get_cfg_var('se.secrets.zoho.webhook_secret'), true)))){
throw new Exceptions\InvalidCredentialsException(); throw new Exceptions\InvalidCredentialsException();
} }