Verified Commit ccdaf4a6 authored by Sebastiaan Deckers's avatar Sebastiaan Deckers 馃悜

feat: GET /sites/domain/configuration

parent 44a3be06
Pipeline #58759946 passed with stage
in 3 minutes and 11 seconds
......@@ -2,9 +2,11 @@
## `GET /sites{/domain}/certificate`
*Not yet implemented.*
Scope: `global_read` or `deploy`
Retrieves TLS public certificate with SAN records, CA chain, and private key as PEM strings.
Retrieve certificate information.
Response body:
```js
{
......@@ -15,16 +17,21 @@ Retrieve certificate information.
'*.example.net',
'a.example.com',
'b.example.com'
]
],
ca: ['...', '...'],
certificate: '...',
key: '...',
}
```
## `PUT /sites{/domain}/certificate`
## `PUT /sites{/domain}/certificates`
*Not yet implemented.*
Issue a certificate with the given Subject Alternative Name (SAN or `subjectAltName`) extension field.
Request body:
```js
{
san: [
......
......@@ -2,7 +2,7 @@ const shellEscape = require('shell-escape')
const { exec } = require('child-process-promise')
const { s3cmd } = require('./s3cmd')
const { join } = require('path')
const { copyFile } = require('fs')
const { promises: { copyFile, readFile } } = require('fs')
const { promisify } = require('util')
const ms = require('ms')
const rimraf = require('rimraf')
......@@ -14,10 +14,10 @@ async function issueCertificate (fastify, domain) {
const certificate = await db.collection('certificates').findOne({ domain })
if (certificate !== null) {
if (certificate.issued instanceof Date) {
const issuedAt = certificate.issued.getTime()
if (!Number.isNaN(issuedAt)) {
const timeSinceIssued = Date.now() - issuedAt
if (certificate.modified instanceof Date) {
const modifiedAt = certificate.modified.getTime()
if (!Number.isNaN(modifiedAt)) {
const timeSinceIssued = Date.now() - modifiedAt
if (timeSinceIssued < ms('30 days')) {
throw new Error(`Not renewing fresh certificate for: ${domain}`)
}
......@@ -52,11 +52,11 @@ async function issueCertificate (fastify, domain) {
}
await Promise.all([
promisify(copyFile)(
copyFile(
join(directory, `${domain}.key`),
join(directory, 'key.pem')
),
promisify(copyFile)(
copyFile(
join(directory, 'fullchain.cer'),
join(directory, 'cert.pem')
)
......@@ -70,13 +70,18 @@ async function issueCertificate (fastify, domain) {
`s3://${configuration.s3.bucket}/sites/${domain}/crypto/`
)
const [key, cert] = await Promise.all([
readFile(join(directory, 'key.pem'), 'utf8'),
readFile(join(directory, 'cert.pem'), 'utf8')
])
await promisify(rimraf)(directory)
await db.collection('certificates').updateOne(
{ domain },
{
$set: { domain },
$currentDate: { issued: true }
$set: { domain, san: domains.slice(1), ca: [], key, cert },
$currentDate: { modified: true }
},
{ upsert: true }
)
......
const jwtPermissions = require('express-jwt-permissions')
const { checkDomainAccess } = require('../../../../hooks/checkDomainAccess')
module.exports = async (fastify, options) => {
fastify.use(
jwtPermissions({ permissionsProperty: 'scope' })
.check([['deploy'], ['global_read']])
)
fastify.route({
method: 'GET',
url: '/sites/:domain/certificates',
schema: {
response: {
200: {
type: 'object',
properties: {
modified: { type: 'string' },
domain: { type: 'string' },
san: {
type: 'array',
items: { type: 'string' }
},
ca: {
type: 'array',
items: { type: 'string' }
},
cert: { type: 'string' },
key: { type: 'string' }
}
}
}
},
preHandler: [
checkDomainAccess()
],
handler: async (request, reply) => {
const { domain } = request.params
const { db } = fastify.mongo
return db.collection('certificates')
.findOne({ domain })
}
})
}
......@@ -54,7 +54,7 @@ module.exports = async (configuration) => {
require('./routes/configurations/get'),
require('./routes/domains/available/get'),
require('./routes/domains/suggestions/get'),
// require('./routes/sites/domain/certificate/get'),
require('./routes/sites/domain/certificate/get'),
// require('./routes/sites/domain/certificate/put'),
require('./routes/sites/domain/configuration/get'),
// require('./routes/sites/domain/configuration/patch'),
......
......@@ -9,7 +9,10 @@ module.exports = (configuration) => {
test('Connecting database', async (t) => {
if (mongo.db === undefined) {
const { url, database } = configuration.mongodb
const client = await MongoClient.connect(url)
const client = await MongoClient.connect(
url,
{ useNewUrlParser: true }
)
mongo.db = client.db(database)
const halt = async () => {
await client.close()
......
const test = require('blue-tape')
const configuration = require('../core.conf.example.js')
const {
getEdgeAccessToken,
getUserAccessToken
} = require('./helpers/credentials')
const { fetch } = require('./helpers/fetch')
const mongo = require('./helpers/database')(configuration)
const server = require('..')
let fastify
test('Start server', async (t) => {
fastify = await server(configuration)
await fastify.listen(configuration.port, configuration.host)
})
const fixture = {
modified: new Date(),
domain: 'example.com',
san: ['www.example.com', 'example.net'],
ca: ['Intermediate CA', 'Root CA'],
key: 'Private key',
cert: 'Public certificate'
}
const expected = {
...fixture,
modified: fixture.modified.toISOString()
}
test('Setup database fixtures', async (t) => {
await mongo.db.collection('certificates').deleteMany()
await mongo.db.collection('certificates').insertMany([fixture])
})
test('Retrieve site configuration as user', async (t) => {
const accessToken = await getUserAccessToken()
const origin = `https://localhost:${configuration.port}`
const url = `${origin}/v2/sites/${fixture.domain}/certificates`
const response = await fetch(url, {
method: 'GET',
headers: { 'authorization': `Bearer ${accessToken}` }
})
t.true(response.ok)
const actual = await response.json()
t.deepEqual(actual, expected)
})
test('Retrieve site configuration as edge', async (t) => {
const accessToken = await getEdgeAccessToken()
const origin = `https://localhost:${configuration.port}`
const url = `${origin}/v2/sites/${fixture.domain}/certificates`
const response = await fetch(url, {
method: 'GET',
headers: { 'authorization': `Bearer ${accessToken}` }
})
t.true(response.ok)
const actual = await response.json()
t.deepEqual(actual, expected)
})
test('Stop server', (t) => fastify.close())
......@@ -92,6 +92,7 @@ test('Deploy a site', async (t) => {
console.log(status)
},
message ({ message }) {
console.log('PubSub received:', message)
if (message.type === 'site-deploy') {
t.deepEqual(message, expectedDeploy)
receivedMessageCount++
......@@ -150,7 +151,12 @@ test('Deploy a site', async (t) => {
const actualCertificate = await mongo.db.collection('certificates')
.findOne({ domain: fixture.domain })
t.not(actualCertificate, null)
t.not(Number.isNaN(new Date(actualCertificate.issued).getTime()))
t.is(actualCertificate.domain, fixture.domain)
t.deepEqual(actualCertificate.san, [])
t.deepEqual(actualCertificate.ca, [])
t.is(actualCertificate.key, 'private key')
t.is(actualCertificate.cert, 'public certificate chain')
t.not(Number.isNaN(new Date(actualCertificate.modified).getTime()))
})
test('Re-deploying too soon does not renew certificate', async (t) => {
......@@ -161,8 +167,8 @@ test('Re-deploying too soon does not renew certificate', async (t) => {
const actualCertificate = await mongo.db.collection('certificates')
.findOne({ domain: fixture.domain })
t.equal(
actualCertificate.issued.getTime(),
expectedCertificate.issued.getTime()
actualCertificate.modified.getTime(),
expectedCertificate.modified.getTime()
)
})
......@@ -170,14 +176,14 @@ test('Re-deploying after some time renews certificate', async (t) => {
const expired = new Date(Date.now() - ms('60 days'))
await mongo.db.collection('certificates').updateOne(
{ domain: fixture.domain },
{ $set: { issued: expired } }
{ $set: { modified: expired } }
)
await deploy(fixture)
await sleep(10000)
const actualCertificate = await mongo.db.collection('certificates')
.findOne({ domain: fixture.domain })
t.notEqual(
actualCertificate.issued.getTime(),
actualCertificate.modified.getTime(),
expired.getTime()
)
})
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment