mirror of
https://github.com/anikeen-com/yaac.git
synced 2026-03-15 06:36:10 +00:00
- new constant Client::VALIDATION_DNS - added Authorization::getDnsChallenge to get the challenge - added Authorization::getTxtRecord to get the TXT record value - added Helper::waitForDNS to provide an easy way to wait for changes - updated documentation to illustrate DNS validation
187 lines
5.8 KiB
Markdown
187 lines
5.8 KiB
Markdown
# yaac - Yet another ACME client
|
|
|
|
Written in PHP, this client aims to be a decoupled LetsEncrypt client, based on ACME V2.
|
|
|
|
## Decoupled from a filesystem or webserver
|
|
|
|
In stead of, for example writing the certificate to the disk under an nginx configuration, this client just returns the
|
|
data (the certificate and private key).
|
|
|
|
## Why
|
|
|
|
Why whould I need this package? At Afosto we run our software in a multi tenant setup, as any other SaaS would do, and
|
|
therefore we cannot make use of the many clients that are already out there.
|
|
|
|
Almost all clients are coupled to a type of webserver or a fixed (set of) domain(s). This package can be extremely
|
|
useful in case you need to dynamically fetch and install certificates.
|
|
|
|
|
|
## Requirements
|
|
|
|
- PHP7+
|
|
- openssl
|
|
- [Flysystem](http://flysystem.thephpleague.com/) (any adapter would do) - to store the Lets Encrypt account information
|
|
|
|
|
|
## Getting started
|
|
|
|
Getting started is easy. First install the client, then you need to construct a flysystem filesystem, instantiate the
|
|
client and you can start requesting certificates.
|
|
|
|
### Installation
|
|
|
|
Installing this package is done easily with composer.
|
|
```bash
|
|
composer require afosto/yaac
|
|
```
|
|
|
|
### Instantiate the client
|
|
|
|
To start the client you need 3 things; a username for your LetsEncrypt account, a bootstrapped Flysystem and you need to
|
|
decide whether you want to issue `Fake LE Intermediate X1` (staging: `MODE_STAGING`) or `Let's Encrypt Authority X3`
|
|
(live: `MODE_LIVE`, use for production) certificates.
|
|
|
|
```php
|
|
use League\Flysystem\Filesystem;
|
|
use League\Flysystem\Adapter\Local;
|
|
use Afosto\Acme\Client;
|
|
|
|
//Prepare flysystem
|
|
$adapter = new Local('data');
|
|
$filesystem = new Filesystem($adapter);
|
|
|
|
//Construct the client
|
|
$client = new Client([
|
|
'username' => 'example@example.org',
|
|
'fs' => $filesystem,
|
|
'mode' => Client::MODE_STAGING,
|
|
]);
|
|
```
|
|
|
|
While you instantiate the client, when needed a new LetsEcrypt account is created and then agrees to the TOS.
|
|
|
|
|
|
### Create an order
|
|
|
|
To start retrieving certificates, we need to create an order first. This is done as follows:
|
|
|
|
```php
|
|
$order = $client->createOrder(['example.org', 'www.example.org']);
|
|
```
|
|
|
|
In the example above the primary domain is followed by a secondary domain(s). Make sure that for each domain you are
|
|
able to prove ownership. As a result the certificate will be valid for all provided domains.
|
|
|
|
### Prove ownership
|
|
|
|
Before you can obtain a certificate for a given domain you need to prove that you own the given domain(s). In this
|
|
example we will show you how to do this for http-01 validation (where serve specific content at a specific url on the
|
|
domain, like: `example.org/.well-known/acme-challenge/*`).
|
|
|
|
Obtain the authorizations for order. For each domain supplied in the create order request an authorization is returned.
|
|
|
|
```php
|
|
$authorizations = $client->authorize($order);
|
|
```
|
|
|
|
|
|
You now have an array of `Authorization` objects. These have the challenges you can use (both `DNS` and `HTTP`) to
|
|
provide proof of ownership.
|
|
|
|
Use the following example to get the HTTP validation going. First obtain the challenges, the next step is to make the
|
|
challenges accessible from
|
|
```php
|
|
foreach ($authorizations as $authorization) {
|
|
$challenge = $authorization->getHttpChallenge();
|
|
|
|
$file = $authorization->getFile($challenge);
|
|
file_put_contents($file->getFilename(), $file->getContents());
|
|
}
|
|
```
|
|
|
|
Now that the challenges are in place and accessible through `example.org/.well-known/acme-challenge/*` we can request
|
|
validation.
|
|
|
|
### Request validation
|
|
|
|
Next step is to request validation of ownership. For each authorization (domain) we ask LetsEncrypt to verify the
|
|
challenge.
|
|
|
|
```php
|
|
foreach ($authorizations as $authorization) {
|
|
$ok = $client->validate($authorization->getHttpChallenge(), 15);
|
|
}
|
|
```
|
|
|
|
The method above will perform 15 attempts to ask LetsEncrypt to validate the challenge (with 1 second intervals) and
|
|
retrieve an updated status (it might take Lets Encrypt a few seconds to validate the challenge).
|
|
|
|
### Alternative ownership validation via DNS
|
|
|
|
You can also use DNS validation - to do this, you will need access to an API for your DNS
|
|
provider to create TXT records for the target domains.
|
|
|
|
```php
|
|
|
|
//store a map of domain=>TXT record we can use to wait with
|
|
$dnsRecords[];
|
|
|
|
foreach ($authorizations as $authorization) {
|
|
$challenge = $authorization->getDnsChallenge();
|
|
|
|
$txtRecord = $authorization->getTxtRecord($challenge);
|
|
|
|
$domain=$authorization->getDomain();
|
|
$validationDomain='_acme-challenge.'.$domain;
|
|
|
|
//remember the record we're about to set
|
|
$dnsRecords[$validationDomain] = $txtRecord;
|
|
|
|
//set TXT record for $validationDomain to $txtRecord value
|
|
//--
|
|
//-- you need to add code for your DNS provider here
|
|
//--
|
|
}
|
|
```
|
|
|
|
A helper is included which will allow you to wait until you can see the
|
|
DNS changes before asking Let's Encrypt to validate it, e.g.
|
|
|
|
```php
|
|
//wait up to 60 seconds for all our DNS updates to propagate
|
|
if (!Helper::waitForDNS($dnsRecords, 60)) {
|
|
throw new \Exception('Unable to verify TXT record update');
|
|
}
|
|
```
|
|
|
|
Once this passes we can ask Let's Encrypt to do the same...
|
|
|
|
```php
|
|
foreach ($authorizations as $authorization) {
|
|
$ok = $client->validate($authorization->getDnsChallenge(), 15);
|
|
}
|
|
```
|
|
|
|
|
|
### Get the certificate
|
|
|
|
Now to know if validation was successful, test if the order is ready as follows:
|
|
|
|
```php
|
|
if ($client->isReady($order)) {
|
|
//The validation was successful.
|
|
}
|
|
```
|
|
|
|
We now know validation was completed and can obtain the certificate. This is done as follows:
|
|
|
|
```php
|
|
$certificate = $client->getCertificate($order);
|
|
```
|
|
|
|
We now have the certificate, to store it on the filesystem:
|
|
```php
|
|
//Store the certificate and private key where you need it
|
|
file_put_contents('certificate.cert', $certificate->getCertificate());
|
|
file_put_contents('private.key', $certificate->getPrivateKey());
|
|
``` |