16. Live Project: Building a Reels App with EC2, S3 and RDS
16.5 Configuration and Shared Code
config.php web root chya baher aahe – browser kadhi ti file maagu shakat nahi. Tyat AWS access key nahi, fakt bucket che naav aani region.
config.php
<?php
// /var/www/reels/config.php – lives OUTSIDE the web root (public/).
// Permissions: sudo chown root:apache config.php && sudo chmod 640 config.php
// (Ubuntu: root:www-data)
// No AWS access keys here: the SDK gets temporary credentials from the EC2 IAM role.
return [
'db_host' => 'reels-db.abcdefgh1234.ap-south-1.rds.amazonaws.com',
'db_port' => 3306,
'db_name' => 'reelsdb',
'db_user' => 'reels_app',
'db_pass' => 'Use-A-Long-Random-Password-Here!',
'db_ssl_ca' => '/etc/pki/rds/global-bundle.pem', // RDS CA bundle ('' = no TLS)
'aws_region' => 'ap-south-1',
's3_bucket' => 'ravindra-reels-media-pune',
's3_prefix' => 'videos/',
'max_video_bytes' => 50 * 1024 * 1024, // 50 MB
'url_ttl' => '+20 minutes', // presigned URL lifetime
'cookie_secure' => true, // false ONLY for plain-http tests
];
sudo mkdir -p /etc/pki/rds
sudo curl -o /etc/pki/rds/global-bundle.pem https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem
sudo chown root:apache /var/www/reels/config.php && sudo chmod 640 /var/www/reels/config.php # Ubuntu: root:www-data
src/bootstrap.php is included by every page. It starts a hardened session, opens PDO to RDS with real prepared statements and TLS, creates the S3 client without any credentials (the SDK finds the IAM role automatically), and provides small helpers: e() for HTML escaping, CSRF tokens, require_login() and json_out().
src/bootstrap.php
<?php
// src/bootstrap.php – shared by every page: config, session, DB, S3 and helpers
declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
use Aws\S3\S3Client;
$CONFIG = require __DIR__ . '/../config.php';
function cfg(string $key): mixed
{
global $CONFIG;
return $CONFIG[$key];
}
// ---------- session: HttpOnly, Secure, SameSite cookies ----------
session_name('REELSSESSID');
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'secure' => cfg('cookie_secure'),
'httponly' => true,
'samesite' => 'Lax',
]);
ini_set('session.use_strict_mode', '1');
session_start();
// ---------- database (RDS MySQL through PDO) ----------
function db(): PDO
{
static $pdo = null;
if ($pdo === null) {
$dsn = sprintf('mysql:host=%s;port=%d;dbname=%s;charset=utf8mb4',
cfg('db_host'), cfg('db_port'), cfg('db_name'));
$opts = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false, // real prepared statements
];
if (cfg('db_ssl_ca') !== '') {
$opts[PDO::MYSQL_ATTR_SSL_CA] = cfg('db_ssl_ca'); // TLS to RDS
}
$pdo = new PDO($dsn, cfg('db_user'), cfg('db_pass'), $opts);
}
return $pdo;
}
// ---------- S3 client: NO keys – credentials come from the EC2 IAM role ----------
function s3(): S3Client
{
static $client = null;
return $client ??= new S3Client([
'version' => 'latest',
'region' => cfg('aws_region'),
]);
}
function presigned_url(string $key): string
{
$cmd = s3()->getCommand('GetObject', ['Bucket' => cfg('s3_bucket'), 'Key' => $key]);
return (string) s3()->createPresignedRequest($cmd, cfg('url_ttl'))->getUri();
}
// ---------- helpers ----------
function e(?string $s): string // escape for HTML output (stops XSS)
{
return htmlspecialchars($s ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function csrf_token(): string
{
if (empty($_SESSION['csrf'])) {
$_SESSION['csrf'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf'];
}
function csrf_check(): void
{
$sent = $_POST['csrf'] ?? ($_SERVER['HTTP_X_CSRF_TOKEN'] ?? '');
if (!is_string($sent) || !hash_equals(csrf_token(), $sent)) {
http_response_code(403);
exit('Invalid CSRF token');
}
}
function current_user(): ?array
{
if (empty($_SESSION['uid'])) {
return null;
}
return ['id' => (int) $_SESSION['uid'], 'username' => (string) $_SESSION['uname']];
}
function require_login(bool $json = false): array
{
$u = current_user();
if ($u === null) {
if ($json) {
json_out(['error' => 'login required'], 401);
}
header('Location: /login.php');
exit;
}
return $u;
}
function json_out(array $data, int $status = 200): never
{
http_response_code($status);
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store');
echo json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
exit;
}
function page_top(string $title): void
{
echo '<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">'
. '<meta name="viewport" content="width=device-width, initial-scale=1">'
. '<title>' . e($title) . ' · Mitrano Reels</title>'
. '<link rel="stylesheet" href="/assets/reels.css"></head><body class="page">'
. '<div class="box"><h1>' . e($title) . '</h1>';
}
function page_bottom(): void
{
echo '</div></body></html>';
}
| Helper | Protects against |
|---|---|
e() = htmlspecialchars |
Stored/reflected XSS in HTML pages |
csrf_token() / csrf_check() with hash_equals |
CSRF – other sites submitting forms as your user |
PDO::ATTR_EMULATE_PREPARES => false |
SQL injection (true server-side prepared statements) |
session_set_cookie_params (HttpOnly, Secure, SameSite) |
Cookie theft by JavaScript, cookies over HTTP, cross-site requests |
S3Client without keys |
Leaked access keys – there are none to leak |
Why this matters for security
The SDK's default credential chain checks environment variables, then ~/.aws/credentials, then the EC2 instance role. Keeping the code free of keys means a leaked Git repository or a readable config.php does not expose your AWS account. Temporary role credentials rotate automatically.
Ravindra Bagale's Tip
For testing, students set cookie_secure to false and forget to set it back to true after adding HTTPS. Set it to true right after Certbot, and check in browser DevTools → Application → Cookies that Secure and HttpOnly are ticked. And never copy config.php into public/!
Ravindra Bagale's Tip – मराठी
Testing साठी students cookie_secure false करतात आणि HTTPS लावल्यावर true करायला विसरतात. Certbot नंतर लगेच true करा आणि browser DevTools → Application → Cookies मध्ये Secure आणि HttpOnly tick आहेत का बघा. आणि config.php कधी public/ मध्ये copy करू नका!
Ravindra Bagale's Tip – हिंदी
Testing के लिए students cookie_secure को false करते हैं और HTTPS लगाने के बाद true करना भूल जाते हैं. Certbot के तुरंत बाद true करो और browser DevTools → Application → Cookies में देखो कि Secure और HttpOnly tick हैं. और config.php को कभी public/ में copy मत करना!
Practice task
Create config.php and src/bootstrap.php, set the permissions shown, and run php -l src/bootstrap.php. Then run sudo -u apache php -r 'var_dump(is_readable("/var/www/reels/config.php"));' (Ubuntu: www-data) to prove PHP-FPM can read it.