diff --git a/docs/runbook.md b/docs/runbook.md index bd2f2b4..b23c2b2 100644 --- a/docs/runbook.md +++ b/docs/runbook.md @@ -8,7 +8,9 @@ state until the key is enforced. * Composer routes (vnext sites): `/packages.json`, `/p2/reason-dev/.json`, `/dist/reason-dev//-.zip`. Key via `Authorization: Bearer` - only. Answer 503 if `SIMPLE_UPDATE_KEY` is unset. + only. Same rule as above: ungated while `SIMPLE_UPDATE_KEY` is unset, so a + Composer site can build against production before the legacy fleet sends the + key. Setting the key gates both routes in the same deploy. This server only reads the bucket. Everything under `packages/`, `dist/`, `meta/` and `p2/` is written by `reason-wp-repo-actions`. diff --git a/includes/Wpup/ComposerRepository.php b/includes/Wpup/ComposerRepository.php index fcea9fd..ade10f7 100644 --- a/includes/Wpup/ComposerRepository.php +++ b/includes/Wpup/ComposerRepository.php @@ -10,10 +10,10 @@ * /p2/reason-dev/.json p2/reason-dev/.json * /dist/reason-dev//-.zip 302 to a presigned URL * - * Every route requires SIMPLE_UPDATE_KEY as "Authorization: Bearer " and - * fails closed (503) when the key isn't configured: premium plugin code sits - * behind these routes. The key is never accepted from the query string here, - * because Composer records download URLs in composer.lock. + * The key rule is the update-checker route's: while SIMPLE_UPDATE_KEY is unset + * the routes are open, and once it is set every route requires it as + * "Authorization: Bearer ". The key is never accepted from the query + * string here, because Composer records download URLs in composer.lock. */ class Wpup_ComposerRepository { const VENDOR = 'reason-dev'; @@ -53,10 +53,7 @@ public function handle($path, Wpup_Headers $headers) { } $path = ltrim((string)$path, '/'); - if ( $this->key === '' ) { - return Wpup_ComposerResponse::error(503, 'The Composer repository is disabled: SIMPLE_UPDATE_KEY is not set.'); - } - if ( !$this->isAuthorized($headers) ) { + if ( $this->key !== '' && !$this->isAuthorized($headers) ) { $response = Wpup_ComposerResponse::error(401, 'Invalid or missing update key.'); $response->headers['WWW-Authenticate'] = 'Bearer realm="packages.reason.com"'; return $response; diff --git a/serverless.yml b/serverless.yml index 2eae31d..ba3044f 100644 --- a/serverless.yml +++ b/serverless.yml @@ -39,8 +39,7 @@ functions: S3_PREFIX: ${env:S3_PREFIX, 'packages/'} # Shared secret that gates both the update-checker route (`get_metadata`/`download`) # and the Composer routes (`/packages.json`, `/p2/...`, `/dist/...`). When empty or - # unset, the update-checker route is ungated, but the Composer routes return 503 - # (they fail closed). Read at deploy time from SSM Parameter Store at + # unset, both are ungated. Read at deploy time from SSM Parameter Store at # /${self:service}//SIMPLE_UPDATE_KEY. Set/rotate it with: # aws ssm put-parameter --region us-east-1 --name /wp-update-server//SIMPLE_UPDATE_KEY --type SecureString --value '' SIMPLE_UPDATE_KEY: ${ssm:/${self:service}/${self:provider.stage}/SIMPLE_UPDATE_KEY, ''} diff --git a/tests/composer-repository-test.php b/tests/composer-repository-test.php index 0aeeeae..59c12a5 100644 --- a/tests/composer-repository-test.php +++ b/tests/composer-repository-test.php @@ -50,10 +50,14 @@ function s3_error($status, $code) { check('not composer: slug that merely starts with p2', $repo->handle('/p2x/', bearer(KEY)) === null); check('isComposerPath: dist', Wpup_ComposerRepository::isComposerPath('/dist/reason-dev/a/a-1.0.zip')); -// --- fail closed when the key is not configured --- +// --- no key configured: open, like the update-checker route --- $mock = new MockHandler(); -$res = make_repo('', $mock)->handle('/packages.json', bearer('anything')); -check('empty key: 503', $res->status === 503); +$mock->append(new Result(array('Body' => Utils::streamFor('{"packages":{}}')))); +$open = make_repo('', $mock); +check('empty key, no header: 200', $open->handle('/packages.json', new Wpup_Headers(array()))->status === 200); +check('empty key, stray bearer: 200', $open->handle('/packages.json', bearer('anything'))->status === 200); +check('empty key: p2 served', $open->handle('/p2/reason-dev/acme-widget.json', new Wpup_Headers(array()))->status === 200); +check('empty key: unknown path still 404', $open->handle('/p2/', new Wpup_Headers(array()))->status === 404); // --- authorization --- $mock = new MockHandler();