Configuration#

Though it’s not a desktop application, Certwrangler adheres to the XDG Base Directory Specification by default. It will load its config from ${XDG_CONFIG_HOME}/certwrangler (or ~/.config/certwrangler.yaml if ${XDG_CONFIG_HOME} is not set). The config location can be overridden by providing the --config option or the environment variable ${CERTWRANGLER_CONFIG}.

Defining certs#

Certwrangler (whether running as a daemon or running once) will attempt to generate certs via ACME that are configured in its config file. The relevant section of the config file looks like this:

certs:
  example.com:
    account_name: default
    subject_name: default
    store_names:
      - default
    common_name: example.com
    alt_names:
      - www.example.com
    key_size: 4096
    wait_timeout: 120

If this is the entire certs section of your config, Certwrangler will attempt to generate a single cert with a common name of example.com and a SAN of www.example.com.

Cert reference#

cert certwrangler.models.Cert[source]

Managed cert definition.

Show JSON schema
{
   "title": "Cert",
   "description": "Managed cert definition.",
   "type": "object",
   "properties": {
      "common_name": {
         "description": "The common name for the cert.",
         "pattern": "^(?:(\\*\\.|[a-zA-Z0-9])(?:[a-zA-Z0-9-_]{0,61}[A-Za-z0-9])?\\.)+[A-Za-z0-9][A-Za-z0-9-_]{0,61}[A-Za-z]$",
         "title": "Common Name",
         "type": "string"
      },
      "account_name": {
         "description": "The name of the configured ACME account the cert should be created under.",
         "title": "Account Name",
         "type": "string"
      },
      "store_names": {
         "description": "A list of the configured stores the cert should be published to.",
         "items": {
            "type": "string"
         },
         "title": "Store Names",
         "type": "array"
      },
      "store_key": {
         "anyOf": [
            {
               "type": "string"
            },
            {
               "type": "null"
            }
         ],
         "default": null,
         "description": "Optional sub-key the cert should be published to in the store. Currently only supported by the vault store driver.",
         "title": "Store Key"
      },
      "subject_name": {
         "default": "default",
         "description": "The name of the configured subject the cert should be created with.",
         "title": "Subject Name",
         "type": "string"
      },
      "alt_names": {
         "description": "A list of alternative names for the cert.",
         "items": {
            "pattern": "^(?:(\\*\\.|[a-zA-Z0-9])(?:[a-zA-Z0-9-_]{0,61}[A-Za-z0-9])?\\.)+[A-Za-z0-9][A-Za-z0-9-_]{0,61}[A-Za-z]$",
            "type": "string"
         },
         "title": "Alt Names",
         "type": "array"
      },
      "wait_timeout": {
         "default": "PT5M",
         "description": "Wait timeout for DNS operations.",
         "format": "duration",
         "title": "Wait Timeout",
         "type": "string"
      },
      "key_size": {
         "default": 2048,
         "description": "The desired size of the RSA key in bits.",
         "title": "Key Size",
         "type": "integer"
      },
      "follow_cnames": {
         "default": true,
         "description": "Whether to follow CNAMEs for DNS operations.",
         "title": "Follow Cnames",
         "type": "boolean"
      },
      "renewal_threshold": {
         "default": "P30D",
         "description": "How many days before a cert expires should it be renewed.",
         "format": "duration",
         "title": "Renewal Threshold",
         "type": "string"
      }
   },
   "required": [
      "common_name",
      "account_name",
      "store_names"
   ]
}

field common_name: Domain [Required]

The common name for the cert.

Constraints:
  • pattern = ^(?:(*.|[a-zA-Z0-9])(?:[a-zA-Z0-9-_]{0,61}[A-Za-z0-9])?.)+[A-Za-z0-9][A-Za-z0-9-_]{0,61}[A-Za-z]$

field account_name: str [Required]

The name of the configured ACME account the cert should be created under.

field store_names: List[str] [Required]

A list of the configured stores the cert should be published to.

field store_key: str | None = None

Optional sub-key the cert should be published to in the store. Currently only supported by the vault store driver.

field subject_name: str = 'default'

The name of the configured subject the cert should be created with.

field alt_names: List[Domain] [Optional]

A list of alternative names for the cert.

field wait_timeout: timedelta = datetime.timedelta(seconds=300)

Wait timeout for DNS operations.

field key_size: int = 2048

The desired size of the RSA key in bits.

field follow_cnames: bool = True

Whether to follow CNAMEs for DNS operations.

field renewal_threshold: Days = datetime.timedelta(days=30)

How many days before a cert expires should it be renewed.

Constraints:
  • func = <function <lambda> at 0x7f21c39359e0>

  • json_schema_input_type = PydanticUndefined

  • return_type = PydanticUndefined

  • when_used = always

ACME accounts#

Each cert is generated with an ACME account, each having an email address, ACME server URL, and keypair (with configurable key size) associated with it.

accounts:
  default:
    emails:
      - this.is.me@example.com
    # This example is set to the staging environment for testing.
    # The default value is the Let's Encrypt production endpoint.
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    key_size: 4096

The account to use can be specified on a per-certificate basis.

ACME account reference#

account certwrangler.models.Account[source]

Managed ACME account definition.

Show JSON schema
{
   "title": "Account",
   "description": "Managed ACME account definition.",
   "type": "object",
   "properties": {
      "emails": {
         "description": "A list of email addresses for the account.",
         "items": {
            "format": "email",
            "type": "string"
         },
         "title": "Emails",
         "type": "array"
      },
      "server": {
         "default": "https://acme-v02.api.letsencrypt.org/directory",
         "description": "The URL of the ACME server.",
         "format": "uri",
         "maxLength": 2083,
         "minLength": 1,
         "title": "Server",
         "type": "string"
      },
      "key_size": {
         "default": 2048,
         "description": "The desired size of the RSA key in bits",
         "title": "Key Size",
         "type": "integer"
      }
   },
   "required": [
      "emails"
   ]
}

field emails: List[EmailStr] [Required]

A list of email addresses for the account.

field server: HttpUrl = HttpUrl('https://acme-v02.api.letsencrypt.org/directory')

The URL of the ACME server.

field key_size: int = 2048

The desired size of the RSA key in bits

ACME validation (solvers)#

Certwrangler will always use ACME DNS-01 validation. Because it runs as a standalone daemon completely separate from the services it’s generating certs for, it cannot use HTTP-01 validation.

Certwrangler will use a configured solver to complete the ACME challenge. Each solver is configured with a list of zones that it should be responsible for. Again from the example config:

solvers:
  default:
    driver: lexicon
    zones:
      # List out the zones that this solver should be used for.
      # This should only be zones, as in an SOA record exists for this FQDN.
      - example.com
    provider_name: linode4
    provider_options:
      # This will pull the token from the LINODE_TOKEN environment variable
      auth_token: $LINODE_TOKEN

In this case, because the cert that was defined above is in the example.com zone, this default solver would be used. This solver is using the linode4 provider, which means it will use the Linode API to create/update the required ACME challenge DNS records.

EdgeDNS Solver Reference#

solver certwrangler.solvers.edgedns.EdgeDNSSolver[source]

Solver powered by Akamai Edge DNS.

Show JSON schema
{
   "title": "EdgeDNSSolver",
   "description": "Solver powered by Akamai Edge DNS.",
   "type": "object",
   "properties": {
      "driver": {
         "const": "edgedns",
         "title": "Driver",
         "type": "string"
      },
      "zones": {
         "description": "A list of DNS zones this solver should be used for.",
         "items": {
            "pattern": "^(?:(\\*\\.|[a-zA-Z0-9])(?:[a-zA-Z0-9-_]{0,61}[A-Za-z0-9])?\\.)+[A-Za-z0-9][A-Za-z0-9-_]{0,61}[A-Za-z]$",
            "type": "string"
         },
         "title": "Zones",
         "type": "array"
      },
      "host": {
         "description": "The Akamai API host.",
         "pattern": "^(?:(\\*\\.|[a-zA-Z0-9])(?:[a-zA-Z0-9-_]{0,61}[A-Za-z0-9])?\\.)+[A-Za-z0-9][A-Za-z0-9-_]{0,61}[A-Za-z]$",
         "title": "Host",
         "type": "string"
      },
      "client_token": {
         "description": "The Akamai API client token.",
         "title": "Client Token",
         "type": "string"
      },
      "client_secret": {
         "description": "The Akamai API client secret.",
         "title": "Client Secret",
         "type": "string"
      },
      "access_token": {
         "description": "The Akamai API access token.",
         "title": "Access Token",
         "type": "string"
      }
   },
   "required": [
      "driver",
      "zones",
      "host",
      "client_token",
      "client_secret",
      "access_token"
   ]
}

field driver: Literal['edgedns'] [Required]
field host: Domain [Required]

The Akamai API host.

Constraints:
  • pattern = ^(?:(*.|[a-zA-Z0-9])(?:[a-zA-Z0-9-_]{0,61}[A-Za-z0-9])?.)+[A-Za-z0-9][A-Za-z0-9-_]{0,61}[A-Za-z]$

field client_token: str [Required]

The Akamai API client token.

field client_secret: str [Required]

The Akamai API client secret.

field access_token: str [Required]

The Akamai API access token.

field zones: List[Domain] [Required]

A list of DNS zones this solver should be used for.

Lexicon Solver Reference#

Certwrangler has a Lexicon-based solver backend to provide support for many DNS providers (including Linode’s DNS Manager). A list of available providers along with their provider-specific options can be found in the Lexicon configuration docs.

solver certwrangler.solvers.lexicon.LexiconSolver[source]

Solver powered by lexicon.

A full list of available providers and options is available at: https://dns-lexicon.readthedocs.io/en/latest/configuration_reference.html

Show JSON schema
{
   "title": "LexiconSolver",
   "description": "Solver powered by lexicon.\n\nA full list of available providers and options is available at:\nhttps://dns-lexicon.readthedocs.io/en/latest/configuration_reference.html",
   "type": "object",
   "properties": {
      "driver": {
         "const": "lexicon",
         "title": "Driver",
         "type": "string"
      },
      "zones": {
         "description": "A list of DNS zones this solver should be used for.",
         "items": {
            "pattern": "^(?:(\\*\\.|[a-zA-Z0-9])(?:[a-zA-Z0-9-_]{0,61}[A-Za-z0-9])?\\.)+[A-Za-z0-9][A-Za-z0-9-_]{0,61}[A-Za-z]$",
            "type": "string"
         },
         "title": "Zones",
         "type": "array"
      },
      "provider_name": {
         "description": "The name of the lexicon provider to use.",
         "title": "Provider Name",
         "type": "string"
      },
      "provider_options": {
         "additionalProperties": true,
         "description": "Provider-specific options.",
         "title": "Provider Options",
         "type": "object"
      }
   },
   "required": [
      "driver",
      "zones",
      "provider_name"
   ]
}

field driver: Literal['lexicon'] [Required]
field provider_name: str [Required]

The name of the lexicon provider to use.

field provider_options: Dict[str, Any] [Optional]

Provider-specific options.

field zones: List[Domain] [Required]

A list of DNS zones this solver should be used for.

Certificate storage#

After generating a cert, Certwrangler will store the cert and its associated private key in one or more configurable stores.

stores:
  # Example vault store.
  default:
    driver: vault
    server: http://localhost:8200
    mount_point: secret
    path: certwrangler
    auth:
      method: approle
      role_id: example_role_id
      secret_id: $CERTWRANGLER_SECRET_ID

  # An example local store for backing up our cert and keys
  backup:
    driver: local
    path: ./test_store

The Vault store is the most commonly used, as storing certs and keys there lets us allow other applications to read their certs/keys out of Vault.

If Certwrangler fails to write to any of its stores, it will retry the write on the next reconciliation loop until it is successful.

The stores used are configurable for any given cert in the certs section of the config. A cert may be published to multiple stores, and it is possible to define multiple instances of the same type of store with different names (for example, in case you wanted to publish the same cert to two different locations in Vault).

Vault store reference#

store certwrangler.stores.vault.VaultStore[source]

Vault storage driver.

Show JSON schema
{
   "title": "VaultStore",
   "description": "Vault storage driver.",
   "type": "object",
   "properties": {
      "driver": {
         "const": "vault",
         "title": "Driver",
         "type": "string"
      },
      "server": {
         "description": "The URI of the vault server.",
         "format": "uri",
         "maxLength": 2083,
         "minLength": 1,
         "title": "Server",
         "type": "string"
      },
      "ca_cert": {
         "anyOf": [
            {
               "format": "path",
               "type": "string"
            },
            {
               "type": "null"
            }
         ],
         "default": null,
         "description": "Optional path to a CA cert for requests to vault.",
         "title": "Ca Cert"
      },
      "mount_point": {
         "description": "Mount point of the secrets engine.",
         "title": "Mount Point",
         "type": "string"
      },
      "path": {
         "description": "Path where secrets should be written.",
         "format": "path",
         "title": "Path",
         "type": "string"
      },
      "version": {
         "default": 2,
         "description": "The version of the vault secrets engine.",
         "enum": [
            1,
            2
         ],
         "title": "Version",
         "type": "integer"
      },
      "auth": {
         "description": "The config for authenticating with vault.",
         "discriminator": {
            "mapping": {
               "approle": "#/$defs/AppRoleAuth",
               "kubernetes": "#/$defs/KubernetesAuth",
               "token": "#/$defs/TokenAuth"
            },
            "propertyName": "method"
         },
         "oneOf": [
            {
               "$ref": "#/$defs/AppRoleAuth"
            },
            {
               "$ref": "#/$defs/TokenAuth"
            },
            {
               "$ref": "#/$defs/KubernetesAuth"
            }
         ],
         "title": "Auth"
      }
   },
   "$defs": {
      "AppRoleAuth": {
         "description": "AppRole auth class.",
         "properties": {
            "method": {
               "const": "approle",
               "title": "Method",
               "type": "string"
            },
            "mount_point": {
               "anyOf": [
                  {
                     "type": "string"
                  },
                  {
                     "type": "null"
                  }
               ],
               "default": null,
               "description": "Optional mount point for the auth method.",
               "title": "Mount Point"
            },
            "role_id": {
               "description": "The AppRole role_id.",
               "title": "Role Id",
               "type": "string"
            },
            "secret_id": {
               "description": "The AppRole secret_id.",
               "title": "Secret Id",
               "type": "string"
            }
         },
         "required": [
            "method",
            "role_id",
            "secret_id"
         ],
         "title": "AppRoleAuth",
         "type": "object"
      },
      "KubernetesAuth": {
         "description": "Kubernetes auth class.",
         "properties": {
            "method": {
               "const": "kubernetes",
               "title": "Method",
               "type": "string"
            },
            "mount_point": {
               "anyOf": [
                  {
                     "type": "string"
                  },
                  {
                     "type": "null"
                  }
               ],
               "default": null,
               "description": "Optional mount point for the auth method.",
               "title": "Mount Point"
            },
            "role": {
               "description": "The name of the role.",
               "title": "Role",
               "type": "string"
            },
            "token_path": {
               "default": "/var/run/secrets/kubernetes.io/serviceaccount/token",
               "description": "The path to the kubernetes service account token.",
               "title": "Token Path",
               "type": "string"
            }
         },
         "required": [
            "method",
            "role"
         ],
         "title": "KubernetesAuth",
         "type": "object"
      },
      "TokenAuth": {
         "description": "Token auth class.",
         "properties": {
            "method": {
               "const": "token",
               "title": "Method",
               "type": "string"
            },
            "token": {
               "description": "The vault token.",
               "title": "Token",
               "type": "string"
            }
         },
         "required": [
            "method",
            "token"
         ],
         "title": "TokenAuth",
         "type": "object"
      }
   },
   "required": [
      "driver",
      "server",
      "mount_point",
      "path",
      "auth"
   ]
}

field driver: Literal['vault'] [Required]
field server: HttpUrl [Required]

The URI of the vault server.

field ca_cert: Path | None = None

Optional path to a CA cert for requests to vault.

field mount_point: str [Required]

Mount point of the secrets engine.

field path: Path [Required]

Path where secrets should be written.

field version: Literal[1, 2] = 2

The version of the vault secrets engine.

field auth: AppRoleAuth | TokenAuth | KubernetesAuth [Required]

The config for authenticating with vault.

Authentication#

Certwrangler must be configured with Vault credentials to use the Vault store. It currently supports AppRole, token, and Kubernetes authentication; see the sections below for configuration details for each.

AppRole#
store certwrangler.stores.vault.AppRoleAuth[source]

AppRole auth class.

Show JSON schema
{
   "title": "AppRoleAuth",
   "description": "AppRole auth class.",
   "type": "object",
   "properties": {
      "method": {
         "const": "approle",
         "title": "Method",
         "type": "string"
      },
      "mount_point": {
         "anyOf": [
            {
               "type": "string"
            },
            {
               "type": "null"
            }
         ],
         "default": null,
         "description": "Optional mount point for the auth method.",
         "title": "Mount Point"
      },
      "role_id": {
         "description": "The AppRole role_id.",
         "title": "Role Id",
         "type": "string"
      },
      "secret_id": {
         "description": "The AppRole secret_id.",
         "title": "Secret Id",
         "type": "string"
      }
   },
   "required": [
      "method",
      "role_id",
      "secret_id"
   ]
}

field method: Literal['approle'] [Required]
field mount_point: str | None = None

Optional mount point for the auth method.

field role_id: str [Required]

The AppRole role_id.

field secret_id: str [Required]

The AppRole secret_id.

Token#
store certwrangler.stores.vault.TokenAuth[source]

Token auth class.

Show JSON schema
{
   "title": "TokenAuth",
   "description": "Token auth class.",
   "type": "object",
   "properties": {
      "method": {
         "const": "token",
         "title": "Method",
         "type": "string"
      },
      "token": {
         "description": "The vault token.",
         "title": "Token",
         "type": "string"
      }
   },
   "required": [
      "method",
      "token"
   ]
}

field method: Literal['token'] [Required]
field token: str [Required]

The vault token.

Kubernetes#
store certwrangler.stores.vault.KubernetesAuth[source]

Kubernetes auth class.

Show JSON schema
{
   "title": "KubernetesAuth",
   "description": "Kubernetes auth class.",
   "type": "object",
   "properties": {
      "method": {
         "const": "kubernetes",
         "title": "Method",
         "type": "string"
      },
      "mount_point": {
         "anyOf": [
            {
               "type": "string"
            },
            {
               "type": "null"
            }
         ],
         "default": null,
         "description": "Optional mount point for the auth method.",
         "title": "Mount Point"
      },
      "role": {
         "description": "The name of the role.",
         "title": "Role",
         "type": "string"
      },
      "token_path": {
         "default": "/var/run/secrets/kubernetes.io/serviceaccount/token",
         "description": "The path to the kubernetes service account token.",
         "title": "Token Path",
         "type": "string"
      }
   },
   "required": [
      "method",
      "role"
   ]
}

field method: Literal['kubernetes'] [Required]
field mount_point: str | None = None

Optional mount point for the auth method.

field role: str [Required]

The name of the role.

field token_path: str = '/var/run/secrets/kubernetes.io/serviceaccount/token'

The path to the kubernetes service account token.

Local store reference#

store certwrangler.stores.local.LocalStore[source]

Local storage driver.

Show JSON schema
{
   "title": "LocalStore",
   "description": "Local storage driver.",
   "type": "object",
   "properties": {
      "driver": {
         "const": "local",
         "title": "Driver",
         "type": "string"
      },
      "path": {
         "description": "Path to where this driver should publish certs.",
         "format": "path",
         "title": "Path",
         "type": "string"
      }
   },
   "required": [
      "driver",
      "path"
   ]
}

field driver: Literal['local'] [Required]
field path: Path [Required]

Path to where this driver should publish certs.

Example Config#

To see how this all fits together, certwrangler.example.yaml contains a complete example:

---

# Certwrangler's config file.

# Text beginning with a dollar sign will be filled in from environment variables.
# If you need a literal dollar sign you must prefix the dollar sign with an additional
# dollar sign, example: $$
# Note that commented out examples will use double dollar signs, you will need to remove
# the extra dollar sign if you uncomment it.

# Default options for running in daemon mode

# daemon:
#   reconciler:
#     # Sleep interval for the reconciler loop.
#     interval: 60
#   metrics:
#     # The path where metrics will be exported on the http server.
#     mount: /metrics
#   http:
#     # Configuration for the http server.
#     host: 127.0.0.1
#     port: 6377
#     server_name: certwrangler
#     # Both the ssl_key_file and ssl_cert_file must be set together,
#     # one cannot be empty if the other is provided.
#     ssl_key_file: null
#     ssl_key_password: null
#     ssl_cert_file: null
#     ssl_ca_certs_file: null
#   # How often the daemon checks the health of its threads in seconds.
#   watchdog_interval: 30

state:
  driver: local
  # This defaults to $${XDG_DATA_HOME}/certwrangler, but the path can be supplied to change this:

  # path: ./test_state

  # To enable encryption of the state you can run 'certwrangler state generate-key' and supply
  # the resulting key as the first item in the encryption_keys list:

  # encryption_keys:
  #   - example_key

accounts:
  default:
    emails:
      - this.is.me@example.com
    # This example is set to the staging environment for testing.
    # The default value is the Let's Encrypt production endpoint.
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    key_size: 4096

subjects:
  # This feature can probably be removed, Let's Encrypt doesn't bother with using them.
  default:
    country: US
    state_or_province: DevLandia
    locality: Dev Town
    organization: Example Org
    organizational_unit: DevOps

stores:
  # Example vault store.
  default:
    driver: vault
    # The vault store defaults to KV version 2.
    # Version 1 can by used by supplying:

    # version: 1

    server: http://localhost:8200
    mount_point: secret
    path: certwrangler
    auth:
      method: approle
      role_id: example_role_id
      secret_id: $CERTWRANGLER_SECRET_ID
    # Token auth is also supported like so:

    # auth:
    #   method: token
    #   token: $$VAULT_TOKEN

  # An example local store for backing up our cert and keys
  backup:
    driver: local
    path: ./test_store

solvers:
  default:
    driver: lexicon
    zones:
      # List out the zones that this solver should be used for.
      # This should only be zones, as in an SOA record exists for this FQDN.
      - example.com
    provider_name: linode4
    provider_options:
      # This will pull the token from the LINODE_TOKEN environment variable
      auth_token: $LINODE_TOKEN

  # An example using the edgedns solver

  # akamai:
  #   driver: edgedns
  #   zones:
  #     - example.com
  #   host: akaa-WWWWWWWWWWWW.luna.akamaiapis.ne
  #   client_token: $$CERTWRANGLER_EDGE_CLIENT_TOKEN
  #   client_secret: $$CERTWRANGLER_EDGE_CLIENT_SECRET
  #   access_token: $$CERTWRANGLER_EDGE_ACCESS_TOKEN

certs:
  example.com:
    account_name: default
    subject_name: default
    store_names:
      - default
      - backup
    # By default, a cert will be published at a path named after the cert ex:
    # salt/shared/foo/example.com. Setting 'store_key' allows specifing an arbitrary
    # string for its path, ex salt/shared/foo/bar
    # store_key: bar
    common_name: example.com
    alt_names:
      - www.example.com
    key_size: 4096
    # The timeout for waiting for DNS propagation.
    # If this expires certwrangler gives up on waiting
    # but leaves the order open to try again on the next run.
    wait_timeout: 120