CHORE: Remove "get-certs" command (#3657)

This commit is contained in:
Tom Limoncelli 2025-07-09 14:01:23 -04:00 committed by GitHub
parent c5e9cd1cb4
commit a0d04a181a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 3 additions and 418 deletions

View file

@ -135,14 +135,14 @@ See [Getting Started](https://docs.dnscontrol.org/getting-started/getting-starte
will upload your DNS records to multiple providers, which means you
can test one while switching to another. We've switched providers 3
times in three years and we've never lost a DNS record.
- **Adopt CI/CD principles to DNS!** At StackOverflow we maintain our
- **Apply CI/CD principles to DNS!** At StackOverflow we maintain our
DNSControl configurations in Git and use our CI system to roll out
changes. Keeping DNS information in a VCS means we have full
history. Using CI enables us to include unit-tests and
system-tests. Remember when you forgot to include a "." at the end
of an MX record? We haven't had that problem since we included a
test to make sure Tom doesn't make that mistake... again.
- **Adopt PR-based updates.** Allow developers to send updates as PRs,
- **Adopt (GitOps) PR-based updates.** Allow developers to send updates as PRs,
which you can review before you approve.
- **Variables save time!** Assign an IP address to a constant and use the
variable name throughout the file. Need to change the IP address
@ -170,7 +170,6 @@ See [dnscontrol-action](https://github.com/koenrh/dnscontrol-action) or [gacts/i
- **REV() will switch from RFC2317 to RFC4183 in v5.0.** This is a breaking change. Warnings are output if your configuration is affected. No date has been announced for v5.0. See https://docs.dnscontrol.org/language-reference/top-level-functions/revcompat
- **NAMEDOTCOM and SOFTLAYER need maintainers!** These providers have no maintainer. Maintainers respond to PRs and fix bugs in a timely manner, and try to stay on top of protocol changes.
- **get-certs/ACME support is frozen and will be removed without notice between now and July 2025.** It has been unsupported since December 2022. If you don't use this feature, do not start. If you do use this feature, migrate ASAP. See discussion in [issues/1400](https://github.com/StackExchange/dnscontrol/issues/1400)
## More info at our website

View file

@ -1,242 +0,0 @@
package commands
import (
"encoding/json"
"errors"
"fmt"
"os"
"regexp"
"strings"
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/acme"
"github.com/StackExchange/dnscontrol/v4/pkg/credsfile"
"github.com/StackExchange/dnscontrol/v4/pkg/normalize"
"github.com/StackExchange/dnscontrol/v4/pkg/printer"
"github.com/urfave/cli/v2"
)
var _ = cmd(catUtils, func() *cli.Command {
var args GetCertsArgs
return &cli.Command{
Name: "get-certs",
Usage: "DEPRECATED: Issue certificates via Let's Encrypt",
Action: func(c *cli.Context) error {
return exit(GetCerts(args))
},
Flags: args.flags(),
Hidden: true,
}
}())
// GetCertsArgs stores the flags and arguments common to cert commands
type GetCertsArgs struct {
GetDNSConfigArgs
GetCredentialsArgs
ACMEServer string
CertsFile string
RenewUnderDays int
CertDirectory string
Email string
AgreeTOS bool
Verbose bool
Vault bool
VaultPath string
Only string
Notify bool
IgnoredProviders string
}
func (args *GetCertsArgs) flags() []cli.Flag {
flags := args.GetDNSConfigArgs.flags()
flags = append(flags, args.GetCredentialsArgs.flags()...)
flags = append(flags, &cli.StringFlag{
Name: "acme",
Destination: &args.ACMEServer,
Value: "live",
Usage: `ACME server to issue against. Give full directory endpoint. Can also use 'staging' or 'live' for standard Let's Encrypt endpoints.`,
})
flags = append(flags, &cli.IntFlag{
Name: "renew",
Destination: &args.RenewUnderDays,
Value: 15,
Usage: `Renew certs with less than this many days remaining`,
})
flags = append(flags, &cli.StringFlag{
Name: "dir",
Destination: &args.CertDirectory,
Value: ".",
Usage: `Directory to store certificates and other data`,
})
flags = append(flags, &cli.StringFlag{
Name: "certConfig",
Destination: &args.CertsFile,
Value: "certs.json",
Usage: `Json file containing list of certificates to issue`,
})
flags = append(flags, &cli.StringFlag{
Name: "email",
Destination: &args.Email,
Value: "",
Usage: `Email to register with let's encrypt`,
})
flags = append(flags, &cli.BoolFlag{
Name: "agreeTOS",
Destination: &args.AgreeTOS,
Usage: `Must provide this to agree to Let's Encrypt terms of service`,
})
flags = append(flags, &cli.BoolFlag{
Name: "vault",
Destination: &args.Vault,
Usage: `Store certificates as secrets in hashicorp vault instead of on disk.`,
})
flags = append(flags, &cli.StringFlag{
Name: "vaultPath",
Destination: &args.VaultPath,
Value: "/secret/certs",
Usage: `Path in vault to store certificates`,
})
flags = append(flags, &cli.StringFlag{
Name: "skip",
Destination: &args.IgnoredProviders,
Value: "",
Usage: `Provider names to not use for challenges (comma separated)`,
})
flags = append(flags, &cli.BoolFlag{
Name: "verbose",
Destination: &args.Verbose,
Usage: "Enable detailed logging (deprecated: use the global -v flag)",
})
flags = append(flags, &cli.BoolFlag{
Name: "notify",
Destination: &args.Notify,
Usage: `set to true to send notifications to configured destinations`,
})
flags = append(flags, &cli.StringFlag{
Name: "only",
Destination: &args.Only,
Usage: `Only check a single cert. Provide cert name.`,
})
return flags
}
// GetCerts implements the get-certs command.
func GetCerts(args GetCertsArgs) error {
fmt.Println(args.JSFile)
// check agree flag
if !args.AgreeTOS {
return errors.New("you must agree to the Let's Encrypt Terms of Service by using -agreeTOS")
}
if args.Email == "" {
return errors.New("must provide email to use for Let's Encrypt registration")
}
// load dns config
cfg, err := GetDNSConfig(args.GetDNSConfigArgs)
if err != nil {
return err
}
errs := normalize.ValidateAndNormalizeConfig(cfg)
if PrintValidationErrors(errs) {
return errors.New("exiting due to validation errors")
}
providerConfigs, err := credsfile.LoadProviderConfigs(args.CredsFile)
if err != nil {
return err
}
notifier, err := InitializeProviders(cfg, providerConfigs, args.Notify)
if err != nil {
return err
}
for _, skip := range strings.Split(args.IgnoredProviders, ",") {
acme.IgnoredProviders[skip] = true
}
// load cert list
certList := []*acme.CertConfig{}
f, err := os.Open(args.CertsFile)
if err != nil {
return err
}
defer f.Close()
dec := json.NewDecoder(f)
err = dec.Decode(&certList)
if err != nil {
return err
}
if len(certList) == 0 {
return errors.New("must provide at least one certificate to issue in cert configuration")
}
if err = validateCertificateList(certList, cfg); err != nil {
return err
}
acmeServer := args.ACMEServer
if acmeServer == "live" {
acmeServer = acme.LetsEncryptLive
} else if acmeServer == "staging" {
acmeServer = acme.LetsEncryptStage
}
var client acme.Client
if args.Vault {
client, err = acme.NewVault(cfg, args.VaultPath, args.Email, acmeServer, notifier)
} else {
client, err = acme.New(cfg, args.CertDirectory, args.Email, acmeServer, notifier)
}
if err != nil {
return err
}
var manyerr error
for _, cert := range certList {
if args.Only != "" && cert.CertName != args.Only {
continue
}
v := args.Verbose || printer.DefaultPrinter.Verbose
issued, err := client.IssueOrRenewCert(cert, args.RenewUnderDays, v)
if issued || err != nil {
notifier.Notify(cert.CertName, "certificate", "Issued new certificate", err, false)
}
if err != nil {
if manyerr == nil {
manyerr = err
} else {
manyerr = fmt.Errorf("%w; %w", manyerr, err)
}
}
}
notifier.Done()
return manyerr
}
var validCertNamesRegex = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_\-]*$`)
func validateCertificateList(certs []*acme.CertConfig, cfg *models.DNSConfig) error {
for _, cert := range certs {
name := cert.CertName
if !validCertNamesRegex.MatchString(name) {
return fmt.Errorf("'%s' is not a valid certificate name. Only alphanumerics, - and _ allowed", name)
}
sans := cert.Names
if len(sans) > 100 {
return fmt.Errorf("certificate '%s' has too many SANs. Max of 100", name)
}
if len(sans) == 0 {
return fmt.Errorf("certificate '%s' needs at least one SAN", name)
}
for _, san := range sans {
d := cfg.DomainContainingFQDN(san)
if d == nil {
return fmt.Errorf("DNS config has no domain that matches SAN '%s'", san)
}
}
}
return nil
}

View file

@ -115,9 +115,6 @@ title: DNSControl
<li>
<a href="https://docs.dnscontrol.org/commands/get-zones">get-zones</a>: Query a provider for zone info
</li>
<li>
<a href="https://docs.dnscontrol.org/commands/get-certs">get-certs</a>: Renew SSL/TLS certs (DEPRECATED)
</li>
</ul>
</div>

View file

@ -164,7 +164,6 @@
* [preview/push](commands/preview-push.md)
* [check-creds](commands/check-creds.md)
* [get-zones](commands/get-zones.md)
* [get-certs](commands/get-certs.md)
* [fmt](commands/fmt.md)
* [creds.json](commands/creds-json.md)
* [Global Flag](commands/globalflags.md)

View file

@ -1,174 +1,6 @@
# *Let's Encrypt* Certificate generation
{% hint style="warning" %}
**WARNING**: This feature is frozen and will be removed without notice between now and July 2025.
It has been unsupported since December 2022.
This feature has no maintainer. There are other projects that do a better job.
If you do use this feature, please migrate to something else ASAP.
**WARNING**: This feature last existed in v4.21.0.
See discussion in [issues/1400](https://github.com/StackExchange/dnscontrol/issues/1400)
{% endhint %}
DNSControl will generate/renew Let's Encrypt certificates using DNS
validation. It is not a complete certificate management system, but
can perform the renewal steps for the system you create. If you
are looking for a complete system, we recommend
[certbot](https://certbot.eff.org/).
The `dnscontrol get-certs` command will obtain or renew TLS
certificates for your managed domains via
[*Let's Encrypt*](https://letsencrypt.org). This can be extremely useful in
situations where other acme clients are problematic. Specifically,
this may be useful if:
- You are already managing DNS records with DNSControl.
- You have a large number of domains or DNS providers in complicated configurations.
- You want **wildcard** certificates, which *require* DNS validation.
At Stack Overflow we have dual-hosted DNS i.e. zones having
nameservers at two different DNS providers. Most Let's Encrypt systems
do not support DNS validation in that case. DNSControl's `get-certs`
command leverages the core DNSControl commands when issuing
certificates, therefore dual-hosted DNS is supported.
## General Process
The `get-certs` command does the following steps:
1. Determine which certificates you would like issued, and which names should belong to each one.
1. Look for existing certs on disk, and see if they have sufficient time remaining until expiration, and that the names match.
1. If updates are needed:
1. Request a new certificate from the acme server.
1. Receive a list of validations to fill.
1. For each validation (usually one per name on the cert):
1. Create a TXT record on the domain with a given secret value.
1. Wait until the authoritative name servers all return the correct value (polls locally).
1. Tell the acme server to validate the record.
1. Receive a new certificate and save it to disk
Because DNS propagation times vary from provider to provider, and
validations are (currently) done serially, this process may take some
time.
## certs.json
This file should be provided to specify which names you would like to get certificates for. You can
specify any number of certificates, with up to 100 SAN entries each. Subject names can contain wildcards if you wish.
The format of the file is a simple JSON array of objects:
{% code title="certs.json" %}
```json
[
{
"cert_name": "mainCert",
"names": [
"example.com.com",
"www.example.com"
]
},
{
"cert_name": "wildcardCert",
"names": [
"example.com",
"*.example.com",
"*.foo.example.com",
"otherdomain.tld",
"*.otherdomain.tld"
]
}
]
```
{% endcode %}
`dnscontrol get-certs` will attempt to issue any certificates referenced by this file, and will renew or re-issue if the certificate we already have is
close to expiry or if the set of subject names changes for a cert.
## Working directory layout
The `get-certs` command is designed to be run from a working directory that contains all of the data we need,
and stores all of the certificates and other data we generate.
You may store this directory in source control or wherever you like. At Stack Overflow we have a dedicated repository for
certificates, but we take care to always encrypt any private keys with [black box](https://github.com/StackExchange/blackbox) before committing.
The working directory should generally contain:
- `certificates` folder for storing all obtained certificates.
- `.letsencrypt` folder for storing *Let's Encrypt* account keys, registrations, and other metadata.
- `certs.json` to describe what certificates to issue.
- `dnsconfig.js` and `creds.json` are the main files for other dnscontrol commands.
```text
┏━━.letsencrypt
┃ ┗━(*Let's Encrypt* account keys and metadata)
┣━━certificates
┃ ┣━━mainCert
┃ ┃ ┣━mainCert.crt
┃ ┃ ┣━mainCert.json
┃ ┃ ┗━mainCert.key
┃ ┗━━wildcardCert
┃ ┣━wildcardCert.crt
┃ ┣━wildcardCert.json
┃ ┗━wildcardCert.key
┣━━certs.json
┣━━creds.json
┗━━dnsconfig.js
```
## Command line flags
### Required Flags
- `--email test@example.com`: Email address to use for *Let's Encrypt* account registration.
- `--agreeTOS`: Indicates that you agree to the [*Let's Encrypt* Subscriber Agreement](https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf)
### Optional Flags
- `--config {dnsconfig.js}`, `--creds {creds.json}` and other flags to find your dns configuration are the same as used for `dnscontrol preview` or `push`. `get-certs` needs to read the dns config so it knows which providers manage which domains, and so it can make sure it is not going to make any destructive changes to your domains. If the `get-certs` command needs to fill a challenge on a domain that has pending corrections, it will abort for safety. You can run `dnscontrol preview` and `dnscontrol push` at that point to verify and push the pending corrections, and then proceed with issuing certificates.
- `--acme {url}`: URL of the acme server you wish to use. For *Let's Encrypt* you can use the presets `live` or `staging` for the standard services. If you are using a custom boulder instance or other acme server, you may specify the full **directory** url. Must be an acme **v2** server.
- `--renew {n}`: `get-certs` will renew certs with less than this many **days** remaining. The default is 15, and certs will be renewed when they are within 15 days of expiration.
- `--dir {d}`: Root directory holding all certificate and account data as described above. Default is current working directory.
- `--certConfig {j}`: Location of certificate config JSON file as described above. Default is `./certs.json`
- `--vault` Store certificates as secrets in hashicorp vault instead of on disk. (default: false)
- `--vaultPath {value}` Path in vault to store certificates (default: "/secret/certs")
- `--skip {p}`: DNS Provider names (comma separated) to skip using as challenge providers. We use this to avoid unnecessary changes to our backup or internal dns providers that wouldn't be a part of the validation flow.
- `--notify` set to true to send notifications to configured destinations (default: false)
- `--only {value}` Only check a single cert. Provide cert name.
## Workflow
This command is intended to be just a small part of a full certificate automation workflow. It only issues certificates, and explicitly does not deal with certificate storage or deployment. We urge caution to secure your private keys for your certificates, as well as the *Let's Encrypt* account private key. We use [black box](https://github.com/StackExchange/blackbox) to securely store private keys in the certificate repo.
This command is intended to be run as frequently as you desire. One workflow would be to check all certificates into a git repository and run a nightly build that:
1. Clones the cert repo, and the dns config repo (if separate).
2. Decrypt or otherwise obtain the *Let's Encrypt* account private key. DNSControl does not need to read any certificate private keys to check or issue certificates.
3. Run `dnscontrol get-certs` with appropriate flags.
4. Encrypt or store any new or updated private keys.
5. Commit and push any changes to the cert repo.
6. Take care to not leave any plain-text private keys on disk.
The push to the certificate repo can trigger further automation to deploy certs to load balancers, cdns, applications and so forth.
## Example script
```shell
#!/bin/bash
set -e
# get and decrypt the files
[ insert your own code here ]
dnscontrol get-certs \
-email "CHANGE_THIS@example.com" \
--acme live \
--skip bind --renew 31 \
--verbose \
--agreeTOS --vault --notify
# Encrypt and save the files
[ insert your own code here ]
```