diff --git a/.editorconfig b/.editorconfig index 91d16ec..5abccad 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,3 +7,6 @@ end_of_line = lf insert_final_newline = true indent_style = space indent_size = 2 + +[Makefile] +indent_style = tab diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7653b18 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +# Files not to include in .zip/.tar.gz archives +# +.git* export-ignore + +# Handle line endings automatically for files detected as text +# and leave all files detected as binary untouched. +* text=auto + +# Make all text files lf formatted +* text eol=lf + diff --git a/.github/workflows/run-all-tests.yml b/.github/workflows/run-tests-pebble.yml similarity index 53% rename from .github/workflows/run-all-tests.yml rename to .github/workflows/run-tests-pebble.yml index e6d80ed..65fed1c 100644 --- a/.github/workflows/run-all-tests.yml +++ b/.github/workflows/run-tests-pebble.yml @@ -1,73 +1,110 @@ -name: Run all tests -on: - push: - branches: - - master - pull_request: - branches: - - master -jobs: - test-alpine: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Build the docker-compose stack - run: docker-compose up -d --build - - name: Run test suite on Alpine - run: test/run-test.sh alpine - test-centos6: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Build the docker-compose stack - run: docker-compose up -d --build - - name: Run test suite on centos6 - run: test/run-test.sh centos6 - test-centos7-duckdns: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Build the docker-compose stack - run: docker-compose up -d --build - - name: Run test suite on CentOS7 against Staging using DuckDNS - run: test/run-test.sh centos7-duckdns - test-debian: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Build the docker-compose stack - run: docker-compose up -d --build - - name: Run test suite on Debian - run: test/run-test.sh debian - test-ubuntu: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Build the docker-compose stack - run: docker-compose up -d --build - - name: Run test suite on Ubuntu - run: test/run-test.sh ubuntu - test-ubuntu16: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Build the docker-compose stack - run: docker-compose up -d --build - - name: Run test suite on Ubuntu16 - run: test/run-test.sh ubuntu16 - test-ubuntu18: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Build the docker-compose stack - run: docker-compose up -d --build - - name: Run test suite on Ubuntu18 - run: test/run-test.sh ubuntu18 - test-ubuntu-duckdns: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Build the docker-compose stack - run: docker-compose up -d --build - - name: Run test suite on Ubuntu against Staging using DuckDNS - run: test/run-test.sh ubuntu-duckdns +name: Run all tests on pebble +on: + push: + paths-ignore: + - '.github/workflows/*' + branches: + - master + pull_request: + branches: + - master + workflow_dispatch: + branches: + - master +jobs: + test-alpine: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on Alpine + run: test/run-test.sh alpine + test-bash-4-0: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on Alpine using Bash 4.0 + run: test/run-test.sh bash4-0 + test-bash-4-2: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on Alpine using Bash 4.2 + run: test/run-test.sh bash4-2 + test-bash-5-0: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on Alpine using Bash 5 + run: test/run-test.sh bash5-0 + test-centos6: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on CentOS6 + run: test/run-test.sh centos6 + test-centos7: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on CentOS7 + run: test/run-test.sh centos7 + test-centos8: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on CentOS8 + run: test/run-test.sh centos8 + test-debian: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on Debian + run: test/run-test.sh debian + test-rockylinux8: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on RockyLinux8 + run: test/run-test.sh rockylinux8 + test-ubuntu: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on Ubuntu + run: test/run-test.sh ubuntu + test-ubuntu16: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on Ubuntu16 + run: test/run-test.sh ubuntu16 + test-ubuntu18: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on Ubuntu18 + run: test/run-test.sh ubuntu18 diff --git a/.github/workflows/run-tests-staging-duckdns.yml b/.github/workflows/run-tests-staging-duckdns.yml new file mode 100644 index 0000000..02805fa --- /dev/null +++ b/.github/workflows/run-tests-staging-duckdns.yml @@ -0,0 +1,34 @@ +name: Run all tests using DuckDNS +on: + push: + paths-ignore: + - '.github/workflows/*' + branches: + - master + pull_request: + branches: + - master + workflow_dispatch: + branches: + - master +env: + DUCKDNS_TOKEN: ${{ secrets.DUCKDNS_TOKEN == '' && '1d616aa9-b8e4-4bb4-b312-3289de82badb' || secrets.DUCKDNS_TOKEN }} +jobs: + test-centos7-duckdns: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on CentOS7 against Staging using DuckDNS + run: test/run-test.sh centos7-duckdns + test-ubuntu-duckdns: + runs-on: ubuntu-latest + if: always() + needs: test-centos7-duckdns + steps: + - uses: actions/checkout@v2 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on Ubuntu against Staging using DuckDNS + run: test/run-test.sh ubuntu-duckdns diff --git a/.github/workflows/run-tests-staging-dynu.yml b/.github/workflows/run-tests-staging-dynu.yml new file mode 100644 index 0000000..cf38bd7 --- /dev/null +++ b/.github/workflows/run-tests-staging-dynu.yml @@ -0,0 +1,34 @@ +name: Run all tests using Dynu +on: + push: + paths-ignore: + - '.github/workflows/*' + branches: + - master + pull_request: + branches: + - master + workflow_dispatch: + branches: + - master +env: + DYNU_API_KEY: ${{ secrets.DYNU_API_KEY == '' && '65cXefd35XbYf36546eg5dYcZT6X52Y2' || secrets.DYNU_API_KEY }} +jobs: + test-centos7-dynu: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on CentOS7 against Staging using Dynu + run: test/run-test.sh centos7-dynu + test-ubuntu-dynu: + runs-on: ubuntu-latest + if: always() + needs: test-centos7-dynu + steps: + - uses: actions/checkout@v2 + - name: Build the docker-compose stack + run: docker-compose up -d --build + - name: Run test suite on Ubuntu against Staging using Dynu + run: test/run-test.sh ubuntu-dynu diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index d5adbf5..52873f6 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -2,16 +2,21 @@ name: shellcheck on: push: + paths-ignore: + - '.github/workflows/*' branches: [ master ] pull_request: branches: [ master ] + workflow_dispatch: + branches: + - master jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Lint check - uses: azohra/shell-linter@v0.2.0 + uses: azohra/shell-linter@latest with: path: "getssl" diff --git a/.github/workflows/stale2.yml b/.github/workflows/stale2.yml index ce6f9c1..94d3686 100644 --- a/.github/workflows/stale2.yml +++ b/.github/workflows/stale2.yml @@ -1,31 +1,16 @@ +name: 'Close stale issues and PRs' on: schedule: - - cron: "0 0 * * *" - -name: Run Stale Bot on Issue Comments + - cron: '45 2 * * *' jobs: - build: - name: stale + stale: runs-on: ubuntu-latest steps: - - uses: actions/checkout@master - - name: stale - uses: gatsbyjs/stale@master + - uses: actions/stale@v3 with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DRY_RUN: true - DAYS_BEFORE_STALE: 60 - DAYS_BEFORE_CLOSE: 30 - STALE_ISSUE_LABEL: 'stale' - STALE_PR_LABEL: 'stale' - OPERATIONS_PER_RUN: 30 - STALE_ISSUE_MESSAGE: 'This issue will be closed as no updates for 60 days' - CLOSE_MESSAGE: 'Closing stale issue after 90 days of inactivity' - EXEMPT_ISSUE_LABELS: | - bug - documentation - enhancement - feature - help wanted - rfc + stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 30 days.' + days-before-stale: 60 + days-before-close: 30 + any-of-labels: 'needs more information' + debug-only: false diff --git a/.gitignore b/.gitignore index 893a4c3..8317ebf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,9 @@ -.history/ -.venv/ +*~ +*# +*.swp +*.tmp +*.bak +*.tdy +*.tar.gz +*.orig +JSON.sh diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 867a6d2..0000000 --- a/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -language: bash - -# Use container-based infrastructure for quicker build start-up -sudo: false - -addons: - apt: - sources: - - debian-sid # Grab shellcheck from the Debian repo (o_O) - packages: - - shellcheck - -script: - - bash -c 'shopt -s globstar; shellcheck getssl' - -matrix: - fast_finish: true diff --git a/Makefile b/Makefile index 4f16126..d88e22c 100644 --- a/Makefile +++ b/Makefile @@ -19,10 +19,9 @@ ifneq ($(strip $(DESTDIR)),) mkdir -p $(DESTDIR) endif - install -Dm755 getssl $(DESTDIR)/usr/bin/getssl - - install -dm755 $(DESTDIR)/usr/share/getssl - cp -r *_scripts $(DESTDIR)/usr/share/getssl + install -Dvm755 getssl $(DESTDIR)/usr/bin/getssl + install -dvm755 $(DESTDIR)/usr/share/getssl + for dir in *_scripts; do install -dv $(DESTDIR)/usr/share/getssl/$$dir; install -pv $$dir/* $(DESTDIR)/usr/share/getssl/$$dir/; done .PHONY: install diff --git a/README.md b/README.md index c359239..1af7ab0 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,31 @@ +# getssl + ![Run all tests](https://github.com/srvrco/getssl/workflows/Run%20all%20tests/badge.svg) ![shellcheck](https://github.com/srvrco/getssl/workflows/shellcheck/badge.svg) -# getssl Obtain SSL certificates from the letsencrypt.org ACME server. Suitable for automating the process on remote servers. +## Table of Contents +- [Features](#features) +- [Installation](#installation) +- [Overview](#overview) +- [Getting started](#getting-started) +- [Detailed guide to getting started with more examples](#detailed-guide-to-getting-started-with-more-examples) +- [Wildcard certificates](#wildcard-certificates) +- [Automating updates](#automating-updates) +- [Structure](#structure) +- [Server-Types](#server-types) +- [Revoke a certificate](#revoke-a-certificate) +- [Elliptic curve keys](#elliptic-curve-keys) +- [Preferred Chain](#preferred-chain) +- [Include Root certificate in full chain](#include-root-certificate-in-full-chain) +- [Issues / problems / help](#issues--problems--help) + +## Upgrade broken in v2.43 + +The automatic upgrade in v2.43 is broken as the url is incorrect. If you have this version installed you'll need to manually upgrade using: +```curl --silent --user-agent getssl/manual https://raw.githubusercontent.com/srvrco/getssl/latest/getssl --output getssl``` + ## Features * **Bash** - It runs on virtually all unix machines, including BSD, most @@ -33,7 +55,7 @@ for automating the process on remote servers. debug information is available. * **Reload services** - After a new certificate is obtained then the relevant services (e.g. apache/nginx/postfix) can be reloaded. -* **ACME v1 and V2** - Supports both ACME versions 1 and 2 +* **ACME v1 and V2** - Supports both ACME versions 1 and 2 (note ACMEv1 is deprecated and clients will automatically use v2) ## Installation @@ -41,7 +63,7 @@ Since the script is only one file, you can use the following command for a quick installation of GetSSL only: ```sh -curl --silent https://raw.githubusercontent.com/srvrco/getssl/master/getssl > getssl ; chmod 700 getssl +curl --silent https://raw.githubusercontent.com/srvrco/getssl/latest/getssl > getssl ; chmod 700 getssl ``` This will copy the getssl Bash script to the current location and change @@ -71,25 +93,28 @@ desktop computer, or even a virtualbox) and add the checks, and certificates to a remote server ( providing you have a ssh with key, sftp or ftp access to the remote server). -```getssl -getssl ver. 2.02 +```getssl -h +getssl ver. 2.36 Obtain SSL certificates from the letsencrypt.org ACME server -Usage: getssl [-h|--help] [-d|--debug] [-c|--create] [-f|--force] [-a|--all] [-q|--quiet] [-Q|--mute] [-u|--upgrade] [-k|--keep #] [-U|--nocheck] [-r|--revoke cert key] [-w working_dir] domain +Usage: getssl [-h|--help] [-d|--debug] [-c|--create] [-f|--force] [-a|--all] [-q|--quiet] [-Q|--mute] [-u|--upgrade] [-X|--experimental tag] [-U|--nocheck] [-r|--revoke cert key] [-w working_dir] [--preferred-chain chain] domain Options: -a, --all Check all certificates - -d, --debug Outputs debug information + -d, --debug Output debug information -c, --create Create default config files -f, --force Force renewal of cert (overrides expiry checks) -h, --help Display this help message and exit + -i, --install Install certificates and reload service -q, --quiet Quiet mode (only outputs on error, success of new cert, or getssl was upgraded) - -Q, --mute Like -q, but mutes notification about successful upgrade + -Q, --mute Like -q, but also mute notification about successful upgrade -r, --revoke "cert" "key" [CA_server] Revoke a certificate (the cert and key are required) - -u, --upgrade Upgrade getssl if a more recent version is available - -k, --keep "#" Maximum amount of old getssl versions to keep when upgrading + -u, --upgrade Upgrade getssl if a more recent version is available - can be used with or without domain(s) + -X --experimental tag Allow upgrade to a specified version of getssl -U, --nocheck Do not check if a more recent version is available + -v --version Display current version of getssl -w working_dir "Working directory" + --preferred-chain "chain" Use an alternate chain for the certificate ``` ## Getting started @@ -149,9 +174,39 @@ Change the server in your config file to get a fully valid certificate. dns. The certificate can be used (and checked with getssl) on alternate ports. +## Detailed guide to getting started with more examples + +[Guide to getting a certificate for example.com and www.example.com](https://github.com/srvrco/getssl/wiki/Guide-to-getting-a-certificate-for-example.com-and-www.example.com) + +## Wildcard certificates + +`getssl` supports creating wildcard certificates, i.e. _*.example.com_ which allows a single certificate to be used for any domain under *example.com*, e.g. *www.example.com*, *mail.example.com*. These must be validated using the dns-01 method. + +A *partial* example `getssl.cfg` file is: + +```sh +VALIDATE_VIA_DNS=true +export CPANEL_USERNAME='' +export CPANEL_URL='https://www.cpanel.host:2083' +export CPANEL_APITOKEN='1ABC2DEF3GHI4JKL5MNO6PQR7STU8VWX9YZA' +DNS_ADD_COMMAND=/home/root/getssl/dns_scripts/dns_add_cpanel +DNS_DEL_COMMAND=/home/root/getssl/dns_scripts/dns_del_cpanel +``` + +Create the wildcard certificate (need to use quotes to prevent globbing): + +```sh +getssl "*.example.domain" +``` + +You can renew the certificate using `getssl -a` to renew all configured certificates. + +You can also specify additional domains in the `SANS` line, e.g. `SANS="www.test.example.com"`. +This cannot contain any of the domains which would be covered by the wildcard certificate. + ## Automating updates -I use the following cron +I use the following **cron** job ```cron 23 5 * * * /root/scripts/getssl -u -a -q @@ -174,12 +229,12 @@ command line). Within the **working directory** is a config file `getssl.cfg` which is a simple bash file containing variables, an example of which is: -```getssl +```sh # Uncomment and modify any variables you need # The staging server is best for testing (hence set as default) -CA="https://acme-staging.api.letsencrypt.org" +CA="https://acme-staging-v02.api.letsencrypt.org" # This server issues full certificates, however has rate limits -#CA="https://acme-v01.api.letsencrypt.org" +#CA="https://acme-v02.api.letsencrypt.org" AGREEMENT="https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf" @@ -200,15 +255,15 @@ then, within the **working directory** there will be a folder for each certificate (based on its domain name). Within that folder will be a config file (again called `getssl.cfg`). An example of which is: -```getssl +```sh # Uncomment and modify any variables you need # see https://github.com/srvrco/getssl/wiki/Config-variables for details # see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs # # The staging server is best for testing -#CA="https://acme-staging.api.letsencrypt.org" +#CA="https://acme-staging-v02.api.letsencrypt.org" # This server issues full certificates, however has rate limits -#CA="https://acme-v01.api.letsencrypt.org" +#CA="https://acme-v02.api.letsencrypt.org" #AGREEMENT="https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf" @@ -239,7 +294,9 @@ DOMAIN_KEY_LOCATION="ssh:server5:/etc/ssl/domain.key" #DOMAIN_PEM_LOCATION="" this is the domain_key. domain cert and CA cert -# The command needed to reload apache / nginx or whatever you use +# The command needed to reload apache / nginx or whatever you use. +# Several (ssh) commands may be given using a bash array: +# RELOAD_CMD=('ssh:sshuserid@server5:systemctl reload httpd' 'logger getssl for server5 efficient.') RELOAD_CMD="service apache2 reload" # Define the server type. This can be https, ftp, ftpi, imap, imaps, pop3, pop3s, smtp, @@ -270,12 +327,12 @@ Multiple locations can be defined for a file by separating the locations with a A typical config file for `example.com` and `www.example.com` on the same server would be: -```getssl +```sh # uncomment and modify any variables you need # The staging server is best for testing -CA="https://acme-staging.api.letsencrypt.org" +CA="https://acme-staging-v02.api.letsencrypt.org" # This server issues full certificates, however has rate limits -#CA="https://acme-v01.api.letsencrypt.org" +#CA="https://acme-v02.api.letsencrypt.org" # additional domains - this could be multiple domains / subdomains in a comma separated list SANS="www.example.com" @@ -325,7 +382,7 @@ Usage: `getssl -r path/to/cert path/to/key [CA_server]` You need to specify both the certificate you want to revoke, and the account or private domain key which was used to sign / obtain the original certificate. The `CA_server` is an optional parameter and -defaults to Let's Encrypt ("") as +defaults to Let's Encrypt ("") as that is currently the only Certificate Authority using the ACME protocol. @@ -337,6 +394,34 @@ key (different of course, don't use the same key for both). prime256v1 secp521r1 (NIST P-521) is included in the code, but not currently supported by Let's Encrypt). +## Preferred Chain + +If a CA offers multiple chains then it is possible to select which chain +is used by using the `PREFERRED_CHAIN` variable in `getssl.cfg` or specifying + `--preferred-chain` in the call to `getssl` + +This uses wildcard matching so requesting "X1" returns the first certificate +returned by the CA which contains the text "X1", Note you may need to escape +any characters which special characters, e.g. +` PREFERRED_CHAIN="\(STAGING\) Doctored Durian Root CA X3"` + +* Staging options are: "(STAGING) Doctored Durian Root CA X3" and "(STAGING) Pretend Pear X1" +* Production options are: "ISRG Root X1" and "ISRG Root X2" + +## Include Root certificate in full chain + +Some servers, including those that use Java keystores, will not accept a server certificate if it cannot valid the full chain of signers. + +Specifically, Nutanix Prism (Element and Central) will not accept the `fullchain.crt` until the root CA's certificate has been appended to it manually. + +If your application requires the full chain, i.e. including the +root certificate of the CA, then this can be included in the `fullchain.crt` file by +adding the following line to `getssl.cfg` + +```sh +FULL_CHAIN_INCLUDE_ROOT="true" +``` + ## Issues / problems / help If you have any issues, please log them at diff --git a/dns_scripts/00GoDaddy-README.txt b/dns_scripts/00GoDaddy-README.txt new file mode 100644 index 0000000..9973556 --- /dev/null +++ b/dns_scripts/00GoDaddy-README.txt @@ -0,0 +1,63 @@ +Using GoDaddy DNS for LetsEncrypt domain validation. + +Quick guide to setting up getssl for domain validation of +GoDaddy DNS domains. + +There are two prerequisites to using getssl with GoDaddy DNS: + +1) Obtain an API access key from developer.godaddy.com + At first sign-up, you will be required to take a "test" key. + This is NOT what you need. Accept it, then get a "Production" + key. At this writing, there is no charge - but you must have + a GoDaddy customer account. + + You must get the API key for the account which owns the domain + that you want to get certificates for. If the domains that you + manage are owned by more than one account, get a key for each. + + The access key consists of a "Key" and a "Secret". You need + both. + +2) Obtain JSON.sh - https://github.com/dominictarr/JSON.sh + +With those in hand, the installation procedure is: + +1) Put JSON.sh in the getssl DNS scripts directory + Default: /usr/share/getssl/dns_scripts + +2) Open your config file (the global file in ~/.getssl/getssl.cfg + or the per-account file in ~/.getssl/example.net/getssl.cfg + +3) Set the following options: + VALIDATE_VIA_DNS="true" + DNS_ADD_COMMAND="/usr/share/getssl/dns_scripts/dns_add_godaddy" + DNS_DEL_COMMAND="/usr/share/getssl/dns_scripts/dns_del_godaddy" + # The API key for your account/this domain + export GODADDY_KEY="..." GODADDY_SECRET="..." + # The base domain name(s) in which the challege records are stored + # E.g. if www.example.net is in the example.net zone: + export GODADDY_BASE="example.com example.net" + + 4) Set any other options that you wish (per the standard + directions.) Use the test CA to make sure that + everything is setup correctly. + +That's it. getssl example.net will now validate with DNS. + +To trace record additions and removals, run getssl as +GODADDY_TRACE=Y getssl example.net + +There are additional options, which are documented in the +*godaddy" files and dns_godaddy -h. + +Copyright (C) 2017, 2018 Timothe Litt litt at acm _dot org + +This sofware may be freely used providing this notice is included with +all copies. The name of the author may not be used to endorse +any other product or derivative work. No warranty is provided +and the user assumes all responsibility for use of this software. + +Report any issues to https://github.com/tlhackque/getssl/issues. + +Enjoy. + diff --git a/dns_scripts/Azure-README.txt b/dns_scripts/Azure-README.txt new file mode 100644 index 0000000..a0dbdc4 --- /dev/null +++ b/dns_scripts/Azure-README.txt @@ -0,0 +1,26 @@ +Using Azure for LetsEncrypt domain verification + +Guide for using Azure for LetsEncrypt domain verification. + +Prerequisites: +- Azure CLI tools installed - see https://docs.microsoft.com/en-us/cli/azure/install-azure-cli +- Logged in with azure-cli - i.e. azure login + +Ensure dns_add_azure and dns_del_azure scripts are called when the DNS is validated by modifying the .getssl.cfg: + +VALIDATE_VIA_DNS=true +DNS_ADD_COMMAND=dns_scripts/dns_add_azure # n.b use valid path +DNS_DEL_COMMAND=dns_scripts/dns_del_azure + +The dns_add_azure and dns_del_azure scripts assume that the following environment variables are added to the configuration file: + +- AZURE_RESOURCE_GROUP - the name of the resource group that contains the DNS zone +- AZURE_ZONE_ID - a comma-separated list of valid DNS zones. this allows the same certificate to be used across multiple top-level domains +- AZURE_SUBSCRIPTION_ID - the name or ID of the subscription that AZURE_RESOURCE_GROUP is part of + +Each of these variables can be included in the .getssl.cfg, e.g: + +export AZURE_RESOURCE_GROUP=my-resource-group +export AZURE_ZONE_ID=example.com,anotherdomain.com +export AZURE_SUBSCRIPTION_ID=my-azure-subscriptin + diff --git a/dns_scripts/Cloudflare-README.md b/dns_scripts/Cloudflare-README.md new file mode 100644 index 0000000..f831cfb --- /dev/null +++ b/dns_scripts/Cloudflare-README.md @@ -0,0 +1,52 @@ +## Using Cloudflare DNS for LetsEncrypt domain validation + +### Enabling the scripts + +Set the following options in `getssl.cfg` (either global or domain-specific): + +``` +VALIDATE_VIA_DNS="true" +DNS_ADD_COMMAND="/usr/share/getssl/dns_scripts/dns_add_cloudflare" +DNS_DEL_COMMAND="/usr/share/getssl/dns_scripts/dns_del_cloudflare" +``` + +### Authentication + +There are 2 methods of authenticating with Cloudflare: + +1. API Keys - Account level, all-purpose tokens +2. API Tokens - Scoped and permissioned access to resources + +Both are configured from your profile in the [Cloudflare dashboard][1] + +[1]: https://dash.cloudflare.com/profile/api-tokens + +#### API Keys + +The **Zone ID** for the domain will be searched for programmatically. + +Set the following options in `getssl.cfg`: + +``` +export CF_EMAIL="..." # Cloudflare account email address +export CF_KEY="..." # Global API Key +``` + +#### API Tokens + +Cloudflare provides a template for creating an API Token with access to edit +zone records. Tokens must be created with at least '**DNS:Edit** permissions +for the domain to add/delete records. + +The API requires higher privileges to be able to list zones, therefore this +method also requires the **Zone ID** from the Overview tab in the Cloudflare +Dashboard. + +Set the following options in the domain-specific `getssl.cfg` + +``` +export CF_API_TOKEN="..." +export CF_ZONE_ID="..." +``` + +__Note__: API Keys will be used instead if also configured diff --git a/dns_scripts/DNS_IONOS.md b/dns_scripts/DNS_IONOS.md new file mode 100644 index 0000000..d3a685e --- /dev/null +++ b/dns_scripts/DNS_IONOS.md @@ -0,0 +1,9 @@ +# Do DNS-01 verification using IONOS DNS API + +The getting started guide explains how to obtain API Keys https://developer.hosting.ionos.de/docs/getstarted + +All API Documentation can be found here https://developer.hosting.ionos.de/docs/dns + +JSON processing in bash is ... hard. So I choose `jq` to do the heavylifting. Other authors choose python so if +you think I did a bad decision feel free to implement this whith python/perl/ruby... + diff --git a/dns_scripts/dns_add_azure b/dns_scripts/dns_add_azure new file mode 100755 index 0000000..3f0f666 --- /dev/null +++ b/dns_scripts/dns_add_azure @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Set the TXT DNS record with azure-cli +fulldomain="${1}" +token="${2}" + +if [[ -z "$AZURE_RESOURCE_GROUP" ]]; then + echo "AZURE_RESOURCE_GROUP is not set. Unable to set TXT records." + exit 2 +fi +if [[ -z "$AZURE_ZONE_ID" ]]; then + echo "AZURE_ZONE_ID is not set. Unable to set TXT records." + exit 2 +fi +if [[ -z "$AZURE_SUBSCRIPTION_ID" ]]; then + echo "AZURE_SUBSCRIPTION_ID is not set. Unable to set TXT records." + exit 2 +fi + +# Determine which zone ID to use from AZURE_ZONE_IDs +# Convert the comma-separated list of AZURE_ZONE_IDs into an array and loop +IFS=',' read -ra zone_ids <<< "$AZURE_ZONE_ID" +for item in "${zone_ids[@]}"; do + # If the full domain ends with the current zone ID + [[ "$fulldomain" =~ .*"${item}"$ ]] && zone_id="$item" +done + +if [ -z "$zone_id" ]; then + echo "${fulldomain} does not match any of the zone IDs specified by ${AZURE_ZONE_ID[@]}" + exit 2 +fi + +az account set --subscription "$AZURE_SUBSCRIPTION_ID" +# Determine the recordset by removing the zone_id from the full domain and prefixing +# with _acme-challenge. +recordset="_acme-challenge.${fulldomain/.$zone_id/}" +# The fulldomain should not be included in the recordset. It is used for subdomains. +# E.g. domain = *.sub.example.com the recordset is _acme-challenge.sub +# domain = example.com the record set is _acme-challenge +[[ "$recordset" == "_acme-challenge.$fulldomain" ]] && recordset="_acme-challenge" +az network dns record-set txt add-record -g "$AZURE_RESOURCE_GROUP" -z "$zone_id" -n "$recordset" -v "$token" diff --git a/dns_scripts/dns_add_challtestsrv b/dns_scripts/dns_add_challtestsrv index 601bcfc..98444b5 100755 --- a/dns_scripts/dns_add_challtestsrv +++ b/dns_scripts/dns_add_challtestsrv @@ -4,4 +4,4 @@ fulldomain="${1}" token="${2}" -curl -X POST -d "{\"host\":\"_acme-challenge.${fulldomain}.\", \"value\": \"${token}\"}" http://10.30.50.3:8055/set-txt +curl --silent -X POST -d "{\"host\":\"_acme-challenge.${fulldomain}.\", \"value\": \"${token}\"}" http://10.30.50.3:8055/set-txt diff --git a/dns_scripts/dns_add_cloudflare b/dns_scripts/dns_add_cloudflare index ea323c1..b1a6a16 100755 --- a/dns_scripts/dns_add_cloudflare +++ b/dns_scripts/dns_add_cloudflare @@ -1,9 +1,11 @@ #!/usr/bin/env bash -# need to add your email address and API key to cloudflare below or set as env variables +# either configure here or export environment variables in getssl.cfg email=${CF_EMAIL:-''} key=${CF_KEY:-''} +api_token=${CF_API_TOKEN:-''} +zone_id=${CF_ZONE_ID:-''} -# This script adds a token to cloudflare DNS for the ACME challenge +# This script adds a TXT record to cloudflare DNS for the ACME challenge # usage dns_add_cloudflare "domain name" "token" # return codes are; # 0 - success @@ -14,7 +16,11 @@ key=${CF_KEY:-''} fulldomain="${1}" token="${2}" API='https://api.cloudflare.com/client/v4/zones' -curl_params=( -H "X-Auth-Email: $email" -H "X-Auth-Key: $key" -H 'Content-Type: application/json' ) +if [[ -z "$api_token" ]]; then + curl_params=( -H "X-Auth-Email: $email" -H "X-Auth-Key: $key" -H 'Content-Type: application/json' ) +else + curl_params=( -H "Authorization: Bearer $api_token" -H 'Content-Type: application/json' ) +fi # check initial parameters @@ -28,84 +34,88 @@ if [[ -z "$token" ]]; then exit 1 fi -if [[ -z "$email" ]]; then - echo "CF_EMAIL (email) parameter not set" - exit 1 -fi +if [[ -z "$api_token" ]]; then + if [[ -z "$email" ]]; then + echo "CF_EMAIL (email) parameter not set" + exit 1 + fi -if [[ -z "$key" ]]; then - echo "CF_KEY (key) parameter not set" - exit 1 + if [[ -z "$key" ]]; then + echo "CF_KEY (key) parameter not set" + exit 1 + fi fi -# get a list of all domain names from cloudflare -# If you have a lot, you may need add "&page=1&per_page=1000" and/or "&status=active" -resp=$(curl --silent "${curl_params[@]}" -X GET "$API") -re='"result":\[(([^][]*\[[^][]*])*[^][]*)]' # find result section -if [[ "${resp// }" =~ $re ]]; then - resp="${BASH_REMATCH[1]}" -fi - -# iterate through all sections to obtain a list of domains -while [[ "$resp" ]]; do - re='[^}{]*\{(([^}{]*\{[^}{]*})*[^}{]*)}(.*)' - if [[ "$resp" =~ $re ]]; then - first="${BASH_REMATCH[1]}" - resp="${BASH_REMATCH[3]}" +if [[ -z "$zone_id" ]]; then + # get a list of all domain names from cloudflare + # If you have a lot, you may need add "&page=1&per_page=1000" and/or "&status=active" + resp=$(curl --silent "${curl_params[@]}" -X GET "$API") + re='"result":\[(([^][]*\[[^][]*])*[^][]*)]' # find result section + if [[ "${resp// }" =~ $re ]]; then + resp="${BASH_REMATCH[1]}" fi - # remove subsections - leave only domain level - while [[ "$first" =~ (.*)[\[\{][^]\{\}[]*[\]\}](.*) ]]; do - first="${BASH_REMATCH[1]}${BASH_REMATCH[2]}" - done - re='"name":"([^"]*)"' - if [[ "$first" =~ $re ]]; then - domains=( "${domains[@]}" "${BASH_REMATCH[1]}" ) - else - echo "Error getting domain name" - exit 2 - fi - re='"id":"([^"]*)"' - if [[ "$first" =~ $re ]]; then - ids=( "${ids[@]}" "${BASH_REMATCH[1]}" ) - else - echo "Error getting domain id" - exit 2 - fi -done -# split required domain name into an array -dnarray=(${fulldomain//./ }) -# get number of parts in required domain name -NumParts=${#dnarray[@]} -# build a test domain name, starting with the largest, and reduce it -# until a match is found, set domain = first ( longest) match. -domain="" -i=1 -while [ $i -lt "$NumParts" ]; do - testdomain="${dnarray[i-1]}" - for ((j=i; j ] [-d|--delete ] [-s|--search ] [-h|--help] [-t|--type] "\ + "[-q|--quiet] [-c|--check] [-S|--status] [-l|--lock #] [-T|--ttl] [-u|--update] [-w|--weight] [-L|--Line]" +} + +help_message() { # print out the help message + cat <<- _EOF_ + $PROGNAME Version. $VERSION + $(usage) + + Options: + -a, --add Add Domain Record 域名 ip (默认类型TXT) + -d, --delete Delete Domain Record 域名 (默认类型TXT) + -s, --search Search Domain Record 域名 + -t, --type Record Type 类型(A、MX、CNAME、TXT、REDIRECT_URL、FORWORD_URL、NS、AAAA、SRV) + _EOF_ +} + +_arg_check(){ + [ -z "$1" ] || _arg_count=$1 + shift + [ ${#} -lt $_arg_count ] && help_message && exit 1 || (echo $2 | grep "^-") && help_message && exit 1 + #If the number of arguments <$_ARG_COUNT print help and exit, and if the second argument begins with “-” print help and exit + return 0 +} + +#[ ${#} -lt 2 ] && help_message && exit 1 #Same as below +#[ -z "$2" ] && help_message && exit 1 #Same as below +_arg_check 2 $@ + +_debug (){ + if [ "$__debug" -eq 1 ]; then + echo -e "\033[1;31m # debug: $(date "+%m %d %T") | Func: ${FUNCNAME[@]} | Line:${BASH_LINENO[@]} \033[0m" "\n $@ " #"Current FUNCNAME ${FUNCNAME} #$LINENO " #"$(($RANDOM%10))" + fi + return 0 +} + +_requires() { + _cmds='' # Check if the commands exists + if [[ "$#" -gt 0 ]]; then + for i in "$@"; do + if eval type type >/dev/null 2>&1; then + eval type "$i" >/dev/null 2>&1 + elif command >/dev/null 2>&1; then + command -v "$i" >/dev/null 2>&1 + else + which "$i" >/dev/null 2>&1 + fi + #[ "$?" -eq 0 ] && _debug "checking for $i exists = ok" || _cmds=$_cmds"$i: " + #shellcheck disable=SC2181 + if [ "$?" -eq 0 ]; then + #_debug "checking for $i exists = ok" + continue + else + _cmds=$_cmds"$i: " + fi + done + else + echo "Usage: _requires [command] " + return 1 + fi + [ -n "$_cmds" ] && { echo -e "\033[1;31m $_cmds command not found \033[0m" && return 1 ;} || return 0 +} + +_requires openssl + +#shellcheck disable=SC2120 +_hex_dump() { #ascii hex + local _str='' + [ $# -gt 0 ] && _str=$@ || read _str + local _str_len=${#_str} + local i=1 + while [ "$i" -le "$_str_len" ]; do + local _str_c="$(printf "%s" "$_str" | cut -c "$i")" + printf " %02x" "'$_str_c" + i=$(($i + 1)) + done + #printf "%s" " 0a" +} + +_urlencode() { + local length="${#1}" + local i='' + for i in $(awk "BEGIN { for ( i=0; i<$length; i++ ) print i }") + do + #local _strc="$(printf "%s" "$1" | cut -c "$i")" #i=1; i<=$length; i++ + local _strc="${1:$i:1}" + case $_strc in [a-zA-Z0-9.~_-]) printf "%s" "$_strc" ;; *) printf "%%%02X" "'$_strc" ;; + esac + done +} + +_signature(){ + signature='' + _hexkey=$(printf "%s" "$AccessKeySecret&" | _hex_dump |sed 's/ //g') + #signature=$(printf "%s" "GET&%2F&$(_urlencode "$query")" | openssl dgst -sha1 -hmac $(printf "%s" "$AccessKeySecret&" | _hex_dump |sed 's/ //g'| xxd -r -p ) -binary | openssl base64 -e) + signature=$(printf "%s" "GET&%2F&$(_urlencode "$query")" | openssl dgst -sha1 -mac HMAC -macopt "hexkey:$_hexkey" -binary | openssl base64 -e) + signature=$(_urlencode "$signature") +} + +_query() { + [ -n "$__type" ] && { [[ "$_Action" = "AddDomainRecord" ]] && _Type="$__type" || { [ "$_Action" = "DescribeDomainRecords" ] && _TypeKeyWord="$__type"; } ; } + query='' + [ -n $AccessKeyId ] && query=$query'AccessKeyId='$AccessKeyId + query=$query'&Action='"$1" + [ -z $_DomainNames ] || query=$query'&DomainName='$_DomainNames + query=$query'&Format=json' + [ -z $_RR ] || query=$query'&RR='$_RR + [ -z $_RRKeyWord ] || query=$query'&RRKeyWord='$_RRKeyWord + [ -z $_RecordId ] || query=$query'&RecordId='$_RecordId + query=$query'&SignatureMethod=HMAC-SHA1' + query=$query"&SignatureNonce=$(date +"%s%N")" + query=$query'&SignatureVersion=1.0' + query=$query'&Timestamp='$_timestamp + [ -z $_Type ] || query=$query'&Type='$_Type + [ -z $_TypeKeyWord ] || query=$query'&TypeKeyWord='$_TypeKeyWord + [ -z $_Value ] || query=$query'&Value='$_Value + [ -z $_ValueKeyWord ] || query=$query'&ValueKeyWord='$_ValueKeyWord + query=$query'&Version=2015-01-09' + #_debug "$query" + _signature + return 0 +} + +_Get_RecordIds(){ + _Action="DescribeDomainRecords" + _query $_Action $_DomainNames + url="${Ali_API}?${query}&Signature=${signature}" + _debug $url + _RecordIds=$(curl -k -s $url | grep -Po 'RecordId[": "]+\K[^"]+') && __delete="1" #RecordId requisite + _debug $_RecordIds + return 0 +} + +__type='TXT' +_DomainNames=$(printf "%s" $1| awk -F"." '{if(NF>=2){print $(NF-1)"."$NF}}') #awk -F\. '{print $(NF-1) FS $NF}') #requisite +_RRKeyWord="_acme-challenge" + +_Get_RecordIds + +_RRKeyWord='' +_TypeKeyWord='' +_ValueKeyWord='' + +if [ "$__delete" = "1" ];then + _Action="DeleteDomainRecord" #Action requisite + _DomainNames='' + for _RecordId in ${_RecordIds[@]} #Delete multiple txt domain record + do + _debug "_RecordId" $_RecordId + _query $_Action $_RecordId + url="${Ali_API}?${query}&Signature=${signature}" + _debug $url + curl -k -s $url && ( echo -e "\n\033[1;32m Aliyun DNS record _acme-challenge.$1 has been deleted \033[0m") + done +else + _Action="AddDomainRecord" #requisite + _RR=$(printf "_acme-challenge.%s" $1| awk -F'.' '{if(NF>2){gsub("."$(NF-1)"."$NF,"");print}}') #requisite + _Value=$2 #requisite + _query $_Action $_DomainNames + url="${Ali_API}?${query}&Signature=${signature}" + _debug $url + curl -k -s $url && (echo -e "\n\033[1;32m Start Checking aliyun DNS record _acme-challenge.$1 \033[0m") + exit 0 +fi diff --git a/dns_scripts/dns_add_duckdns b/dns_scripts/dns_add_duckdns index ef40efe..9d1776a 100755 --- a/dns_scripts/dns_add_duckdns +++ b/dns_scripts/dns_add_duckdns @@ -10,8 +10,17 @@ fi domain="$1" txtvalue="$2" +i=1 + +response=$(curl --retry 5 --silent "https://www.duckdns.org/update?domains=${domain}&token=${token}&txt=${txtvalue}") + +while [[ "${response}" == *"502 Bad Gateway"* ]] && [ $i -le 5 ]; do + echo "Retrying Bad Gateway response (attempt $i of 5)" + sleep 5 + i=$((i+1)) + response=$(curl --retry 5 --silent "https://www.duckdns.org/update?domains=${domain}&token=${token}&txt=${txtvalue}") +done -response=$(curl --silent "https://www.duckdns.org/update?domains=${domain}&token=${token}&txt=${txtvalue}") if [ "$response" != "OK" ]; then echo "Failed to update TXT record for ${domain} at duckdns.org (is the TOKEN valid?)" echo "Response: $response" diff --git a/dns_scripts/dns_add_dynu b/dns_scripts/dns_add_dynu new file mode 100755 index 0000000..e20470d --- /dev/null +++ b/dns_scripts/dns_add_dynu @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# Need to add your API key below or set as env variable +apikey=${DYNU_API_KEY:-''} + +# This script adds a token to dynu.com DNS for the ACME challenge +# usage dns_add_dynu "domain name" "token" +# return codes are; +# 0 - success +# 1 - error in input +# 2 - error within internal processing +# 3 - error in result ( domain not found in dynu.com etc) + +fulldomain="${1}" +token="${2}" + +API='https://api.dynu.com/v2/dns' + +# Check initial parameters +if [[ -z "$fulldomain" ]]; then + echo "DNS script requires full domain name as first parameter" + exit 1 +fi +if [[ -z "$token" ]]; then + echo "DNS script requires challenge token as second parameter" + exit 1 +fi + +curl_params=( -H "accept: application/json" -H "API-Key: $apikey" -H 'Content-Type: application/json' ) + +# Get domain id +# curl -X GET https://api.dynu.com/v2/dns/getroot/ubuntu-getssl.freeddns.org +resp=$(curl --silent "${curl_params[@]}" -X GET "$API/getroot/${fulldomain}") + +# Match domain id +re="\"id\":([^,]*),\"domainName\":\"${fulldomain}\"" +if [[ "$resp" =~ $re ]]; then + domain_id="${BASH_REMATCH[1]}" +fi + +if [[ -z "$domain_id" ]]; then + echo 'Domain name not found on your Dynu account' + exit 3 +fi + +# Check for existing _acme-challenge TXT record +# curl -X GET "https://api.dynu.com/v2/dns/record/_acme-challenge.ubuntu-getssl.freeddns.org?recordType=TXT" +resp=$(curl --silent "${curl_params[@]}" -X GET "${API}/record/_acme-challenge.${fulldomain}?recordType=TXT") +re="\"id\":([^,]*)" +if [[ "$resp" =~ $re ]]; then + record_id="${BASH_REMATCH[1]}" +fi + +if [[ -z "$record_id" ]]; then + # Add new TXT challenge record + resp=$(curl --silent \ + "${curl_params[@]}" \ + -X POST "${API}/${domain_id}/record" \ + --data "{\"nodeName\":\"_acme-challenge\",\"recordType\":\"TXT\",\"state\":\"true\",\"textData\":\"$token\"}") +else + # Update existing record + # curl -X POST https://api.dynu.com/v2/dns/9329328/record/7082063 -d "{\"nodeName\":\"_acme-challenge\",\"recordType\":\"TXT\",\"state\":\"true\",\"textData\":\"Test2\"}" + resp=$(curl --silent \ + "${curl_params[@]}" \ + -X POST "${API}/${domain_id}/record/${record_id}" \ + --data "{\"nodeName\":\"_acme-challenge\",\"recordType\":\"TXT\",\"state\":\"true\",\"textData\":\"$token\"}") +fi + +# If adding record failed (exception:) then print error message +if [[ "$resp" != *"\"statusCode\":200"* ]]; then + echo "Error: DNS challenge not added: unknown error - ${resp}" + exit 3 +fi diff --git a/dns_scripts/dns_add_godaddy b/dns_scripts/dns_add_godaddy index dfd3b3b..f9be745 100755 --- a/dns_scripts/dns_add_godaddy +++ b/dns_scripts/dns_add_godaddy @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (2017) Timothe Litt litt at acm _dot org +# Copyright (C) 2017, 2018 Timothe Litt litt at acm _dot org # Add token to GoDaddy dns using dns_godaddy @@ -36,5 +36,6 @@ fi export GODADDY_KEY export GODADDY_SECRET +export GODADDY_BASE $GODADDY_SCRIPT -q add "${fulldomain}" "_acme-challenge.${fulldomain}." "${token}" diff --git a/dns_scripts/dns_add_ionos b/dns_scripts/dns_add_ionos new file mode 100755 index 0000000..e4354bf --- /dev/null +++ b/dns_scripts/dns_add_ionos @@ -0,0 +1,54 @@ +#!/usr/bin/bash +# +# Called as +# +# eval "${DNS_ADD_COMMAND}" "${lower_d}" "${auth_key}" +# + +# See https://developer.hosting.ionos.de/docs/getstarted how to generate +# an API Key consisting of prefix and key +# +# see DNS API Doc here https://developer.hosting.ionos.de/docs/dns +# + +API_KEY="X-API-Key: ." +API_URL="https://api.hosting.ionos.com/dns/v1" + +# TODO: check $1,$2 not empty + +DNS_RR=$1 +DNS_SECRET=$2 + + +# get zone id: +curl -s -X GET "$API_URL/zones" -H "accept: application/json" -H "Content-Type: application/json" -H "$API_KEY" \ + | jq -r 'map([.name, .id] | join (";")) | .[]' >/tmp/$$.zones + +ZONE=$DNS_RR + +do=true +while $do; do + ZONE_ID=$(awk -F\; '/^'"$ZONE"';/{print $2}' &2 <&2 exit 1 # Intent was to change DNS, so this is an error diff --git a/dns_scripts/dns_route53.py b/dns_scripts/dns_route53.py index a972dfa..6b88b37 100755 --- a/dns_scripts/dns_route53.py +++ b/dns_scripts/dns_route53.py @@ -31,7 +31,7 @@ for zone in response['HostedZones']: if not zone['Config']['PrivateZone']: zone_list[zone['Name']] = zone['Id'] -for key in sorted(zone_list.iterkeys(), key=len, reverse=True): +for key in sorted(zone_list.keys(), key=len, reverse=True): if ".{z}".format(z=key) in ".{z}.".format(z=fqdn): zone_id = zone_list[key] @@ -70,7 +70,7 @@ if action == 'UPSERT': try: my_resolver = dns.resolver.Resolver(configure=False) my_resolver.nameservers = ['8.8.8.8', '8.8.4.4'] - results = my_resolver.query(challenge_fqdn, 'TXT') + results = my_resolver.resolve(challenge_fqdn, 'TXT') data = str(results.response.answer[0][0]).strip('\"') if data == challenge: print("found {f} entry".format(f=challenge_fqdn)) diff --git a/docker-compose.yml b/docker-compose.yml index ec5c24a..f2b1489 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,7 @@ services: environment: # with Go 1.13.x which defaults TLS 1.3 to on GODEBUG: "tls13=1" + PEBBLE_ALTERNATE_ROOTS: 2 ports: - 14000:14000 # HTTPS ACME API - 15000:15000 # HTTPS Management API diff --git a/getssl b/getssl index 96f7423..0a9d28d 100755 --- a/getssl +++ b/getssl @@ -214,61 +214,155 @@ # 2020-02-13 Fix bug with copying to all locations when creating RSA and ECDSA certs (2.20) # 2020-02-22 Change sign_string to use openssl asn1parse (better fix for #424) # 2020-02-23 Add dig to config check for systems without drill (ubuntu) +# 2020-03-11 Use dig +trace to find primary name server and improve dig parsing of CNAME +# 2020-03-12 Fix bug with DNS validation and multiple domains (#524) +# 2020-03-24 Find primary ns using all dns utils (dig, host, nslookup) +# 2020-03-23 Fix staging server URL in domain template (2.21) +# 2020-03-30 Fix error message find_dns_utils from over version of "command" +# 2020-03-30 Fix problems if domain name isn't in lowercase (2.22) +# 2020-04-16 Add alternative working dirs '/etc/getssl/' '${PROGDIR}/conf' '${PROGDIR}/.getssl' +# 2020-04-16 Add -i|--install command line option (2.23) +# 2020-04-19 Remove dependency on seq, ensure clean_up doesn't try to delete /tmp (2.24) +# 2020-04-20 Check for domain using all DNS utilities (2.25) +# 2020-04-22 Fix HAS_HOST and HAS_NSLOOKUP checks - wolfaba +# 2020-04-22 Fix domain case conversion for different locales - glynge (2.26) +# 2020-04-26 Fixed ipv4 confirmation with nslookup - Cyber1000 +# 2020-04-29 Fix ftp/sftp problems if challenge starts with a dash +# 2020-05-06 Fix missing fullchain.ec.crt when creating dual certificates (2.27) +# 2020-05-14 Add --notify-valid option (exit 2 if certificate is valid) +# 2020-05-23 Fix --revoke (didn't work with ACMEv02) (2.28) +# 2020-06-06 Fix missing URL_revoke definition when no CA directory suffix (#566) +# 2020-06-18 Fix CHECK_REMOTE for DUAL_RSA_ECDSA (#570) +# 2020-07-14 Support space separated SANS (#574) (2.29) +# 2020-08-06 Use -sigalgs instead of -cipher when checking remote for tls1.3 (#570) +# 2020-08-31 Fix slow fork bomb when directory containing getssl isn't writeable (#440) +# 2020-09-01 Use RSA-PSS when checking remote for DUAL_RSA_ECDSA (#570) +# 2020-09-02 Fix issue when SANS is space and comma separated (#579) (2.30) +# 2020-10-02 Various fixes to get_auth_dns and changes to support unit tests (#308) +# 2020-10-04 Add CHECK_PUBLIC_DNS_SERVER to check the DNS challenge has been updated there +# 2020-10-13 Bugfix: strip comments in drill/dig output (mhameed) +# 2020-11-18 Wildcard support (#347)(#400)(2.31) +# 2020-12-08 Fix mktemp template on alpine (#612) +# 2020-12-17 Fix delimiter issues with ${alldomains[]} in create_csr (#614)(vietw) +# 2020-12-18 Wrong SANS when domain contains a minus character (atisne) +# 2020-12-22 Fixes to get_auth_dns +# 2020-12-22 Check that dig doesn't return an error (#611)(2.32) +# 2020-12-29 Fix dig SOA lookup (#617)(2.33) +# 2021-01-05 Show error if running in POSIX mode (#611) +# 2021-01-16 Fix double slash when using root directory with DAVS (ionos) +# 2021-01-22 Add FTP_OPTIONS +# 2021-01-27 Add the ability to set several reload commands (atisne) +# 2021-01-29 Use dig -r (if supported) to ignore.digrc (#630) +# 2021-02-07 Allow -u --upgrade without any domain, so that one can only update the script (Benno-K)(2.34) +# 2021-02-09 Prevent listing the complete file if version tag missing (#637)(softins) +# 2021-02-12 Add PREFERRED_CHAIN +# 2021-02-15 ADD ftp explicit SSL with curl for upload the challenge (CoolMischa) +# 2021-02-18 Add FULL_CHAIN_INCLUDE_ROOT +# 2021-03-25 Fix DNS challenge completion check if CNAMEs on different NS are used (sideeffect42)(2.35) +# 2021-05-08 Merge from tlhackque/getssl: GoDaddy, split-view, tempfile permissions fixes, --version(2.36) +# 2021-07-07 Request new certificate if SANs have changed (#669)(#673) +# 2021-07-12 Do not redirect outputs on remote commands when the debug option is used (atisne) +# 2021-07-20 Use +noidnout to enable certificates for IDN domains (#679)(2.37) +# 2021-07-22 Only pass +noidnout param to dig/drill(#682)(2.38) +# 2021-07-25 Fix copy_file_to_location failures with ssh when suffix applied to file lacking an extension (tlhackque)(#686) +# 2021-07-27 Support ftps://, FTPS_OPTIONS, remove default --insecure parameter to ftpes. Report caller(s) of error_exit in debug and test modes (tlhackque)(#687)(2.39) +# 2021-07-30 Prefer API V2 when both offered (tlhackque) (#690) (2.40) +# 2021-07-30 Run tests with -d to catch intermittent failures, Use fork's repo for upgrade tests. (tlhackque) (#692) (2.41) +# 2021-08-26 Improve upgrade check & make upgrade do a full install when possible (tlhackque) (#694) (2.42) +# 2021-09-02 Fix version compare - cURL v8 may have single digit minor numbers. (tlhackque) (2.43) +# 2021-09-26 Delete key file when key algorithm has changed (makuhama) +# 2021-09-30 better error if curl returns 60 (#709) +# 2021-10-01 Fix -preferred-chain argument (#712) +# 2021-10-01 Show help if no domain specified (#705)(2.44) +# 2021-10-08 Extract release tag from release api using awk (fix BSD issues) +# 2021-10-11 Fix broken upgrade url (#718)(2.45) # ---------------------------------------------------------------------------------------- +case :$SHELLOPTS: in + *:posix:*) echo -e "${0##*/}: Running with POSIX mode enabled is not supported" >&2; exit 1;; +esac + PROGNAME=${0##*/} -VERSION="2.20" +PROGDIR="$(cd "$(dirname "$0")" || exit; pwd -P;)" +VERSION="2.45" # defaults ACCOUNT_KEY_LENGTH=4096 ACCOUNT_KEY_TYPE="rsa" -CA="https://acme-staging-v02.api.letsencrypt.org/directory" CA_CERT_LOCATION="" +CA="https://acme-staging-v02.api.letsencrypt.org/directory" CHALLENGE_CHECK_TYPE="http" -CHECK_ALL_AUTH_DNS="false" -CHECK_REMOTE="true" CHECK_REMOTE_WAIT=0 -CODE_LOCATION="https://raw.githubusercontent.com/srvrco/getssl/master/getssl" +CHECK_REMOTE="true" +if [[ -n "${GITHUB_REPOSITORY}" ]] ; then + CODE_LOCATION="https://raw.githubusercontent.com/${GITHUB_REPOSITORY}/master/getssl" + RELEASE_API="https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/latest" +else + CODE_LOCATION="https://raw.githubusercontent.com/srvrco/getssl/master/getssl" + RELEASE_API="https://api.github.com/repos/srvrco/getssl/releases/latest" +fi CSR_SUBJECT="/" CURL_USERAGENT="${PROGNAME}/${VERSION}" DEACTIVATE_AUTH="false" DEFAULT_REVOKE_CA="https://acme-v02.api.letsencrypt.org" -DNS_EXTRA_WAIT="" -DNS_WAIT=10 DOMAIN_KEY_LENGTH=4096 DUAL_RSA_ECDSA="false" +FTP_OPTIONS="" +FTPS_OPTIONS="" +FULL_CHAIN_INCLUDE_ROOT="false" GETSSL_IGNORE_CP_PRESERVE="false" HTTP_TOKEN_CHECK_WAIT=0 IGNORE_DIRECTORY_DOMAIN="false" +OCSP_MUST_STAPLE="false" ORIG_UMASK=$(umask) +PREFERRED_CHAIN="" # Set this to use an alternative root certificate PREVIOUSLY_VALIDATED="true" PRIVATE_KEY_ALG="rsa" -PUBLIC_DNS_SERVER="" RELOAD_CMD="" RENEW_ALLOW="30" REUSE_PRIVATE_KEY="true" SERVER_TYPE="https" SKIP_HTTP_TOKEN_CHECK="false" SSLCONF="$(openssl version -d 2>/dev/null| cut -d\" -f2)/openssl.cnf" -OCSP_MUST_STAPLE="false" -TEMP_UPGRADE_FILE="" TOKEN_USER_ID="" USE_SINGLE_ACL="false" -VALIDATE_VIA_DNS="" -WORKING_DIR=~/.getssl +WORKING_DIR_CANDIDATES=("/etc/getssl" "${PROGDIR}/conf" "${PROGDIR}/.getssl" "${HOME}/.getssl") + +# Variables used when validating using a DNS entry +VALIDATE_VIA_DNS="" # Set this to "true" to enable DNS validation +export AUTH_DNS_SERVER="" # Use this DNS server to check the challenge token has been set +export DNS_CHECK_OPTIONS="" # Options (such as TSIG file) required by DNS_CHECK_FUNC +export PUBLIC_DNS_SERVER="" # Use this DNS server to find the authoritative DNS servers for the domain +CHECK_ALL_AUTH_DNS="false" # Check the challenge token has been set on all authoritative DNS servers +CHECK_PUBLIC_DNS_SERVER="true" # Check the public DNS server as well as the authoritative DNS servers +DNS_ADD_COMMAND="" # Use this command/script to add the challenge token to the DNS entries for the domain +DNS_DEL_COMMAND="" # Use this command/script to remove the challenge token from the DNS entries for the domain +DNS_WAIT_COUNT=100 # How many times to wait for the DNS record to update +DNS_WAIT=10 # How long to wait before checking the DNS record again +DNS_EXTRA_WAIT=60 # How long to wait after the DNS entries are visible to us before telling the ACME server to check. +DNS_WAIT_RETRY_ADD="false" # Try the dns_add_command again if the DNS record hasn't updated + +# Private variables _CHECK_ALL=0 _CREATE_CONFIG=0 +_CURL_VERSION="" _FORCE_RENEW=0 -_KEEP_VERSIONS="" _MUTE=0 +_NOTIFY_VALID=0 +_NOMETER="" _QUIET=0 _RECREATE_CSR=0 +_REDIRECT_OUTPUT="1>/dev/null 2>&1" _REVOKE=0 +_TEST_SKIP_CNAME_CALL=0 +_TEST_SKIP_SOA_CALL=0 _UPGRADE=0 _UPGRADE_CHECK=1 +_UPGRADE_TO_TAG="" _USE_DEBUG=0 +_ONLY_CHECK_CONFIG=0 config_errors="false" -LANG=C +export LANG=C API=1 # store copy of original command in case of upgrading script and re-running @@ -276,6 +370,18 @@ ORIGCMD="$0 $*" # Define all functions (in alphabetical order) +auto_upgrade_v2() { # Automatically update clients to v2 + if [[ "${CA}" == *"acme-v01."* ]] || [[ "${CA}" == *"acme-staging."* ]]; then + OLDCA=${CA} + # shellcheck disable=SC2001 + CA=$(echo "${OLDCA}" | sed "s/v01/v02/g") + # shellcheck disable=SC2001 + CA=$(echo "${CA}" | sed "s/staging/staging-v02/g") + info "Upgraded to v2 (changed ${OLDCA} to ${CA})" + fi + debug "Using certificate issuer: ${CA}" +} + cert_archive() { # Archive certificate file by copying files to dated archive dir. debug "creating an archive copy of current new certs" date_time=$(date +%Y_%m_%d_%H_%M) @@ -298,6 +404,79 @@ cert_archive() { # Archive certificate file by copying files to dated archive d purge_archive "$DOMAIN_DIR" } +cert_install() { # copy certs to the correct location (creating concatenated files as required) + umask 077 + + copy_file_to_location "domain certificate" "$CERT_FILE" "$DOMAIN_CERT_LOCATION" + copy_file_to_location "private key" "$DOMAIN_DIR/${DOMAIN}.key" "$DOMAIN_KEY_LOCATION" + copy_file_to_location "CA certificate" "$CA_CERT" "$CA_CERT_LOCATION" + if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then + if [[ -n "$DOMAIN_CERT_LOCATION" ]]; then + copy_file_to_location "ec domain certificate" \ + "${CERT_FILE%.*}.ec.crt" \ + "${DOMAIN_CERT_LOCATION}" \ + "ec" + fi + if [[ -n "$DOMAIN_KEY_LOCATION" ]]; then + copy_file_to_location "ec private key" \ + "$DOMAIN_DIR/${DOMAIN}.ec.key" \ + "${DOMAIN_KEY_LOCATION}" \ + "ec" + fi + if [[ -n "$CA_CERT_LOCATION" ]]; then + copy_file_to_location "ec CA certificate" \ + "${CA_CERT%.*}.ec.crt" \ + "${CA_CERT_LOCATION%.*}.crt" \ + "ec" + fi + fi + + # if DOMAIN_CHAIN_LOCATION is not blank, then create and copy file. + if [[ -n "$DOMAIN_CHAIN_LOCATION" ]]; then + if [[ "$(dirname "$DOMAIN_CHAIN_LOCATION")" == "." ]]; then + to_location="${DOMAIN_DIR}/${DOMAIN_CHAIN_LOCATION}" + else + to_location="${DOMAIN_CHAIN_LOCATION}" + fi + cat "$FULL_CHAIN" > "$TEMP_DIR/${DOMAIN}_chain.pem" + copy_file_to_location "full chain" "$TEMP_DIR/${DOMAIN}_chain.pem" "$to_location" + if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then + cat "${CERT_FILE%.*}.ec.crt" "${CA_CERT%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}_chain.pem.ec" + copy_file_to_location "full chain" "$TEMP_DIR/${DOMAIN}_chain.pem.ec" "${to_location}" "ec" + fi + fi + # if DOMAIN_KEY_CERT_LOCATION is not blank, then create and copy file. + if [[ -n "$DOMAIN_KEY_CERT_LOCATION" ]]; then + if [[ "$(dirname "$DOMAIN_KEY_CERT_LOCATION")" == "." ]]; then + to_location="${DOMAIN_DIR}/${DOMAIN_KEY_CERT_LOCATION}" + else + to_location="${DOMAIN_KEY_CERT_LOCATION}" + fi + cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" > "$TEMP_DIR/${DOMAIN}_K_C.pem" + copy_file_to_location "private key and domain cert pem" "$TEMP_DIR/${DOMAIN}_K_C.pem" "$to_location" + if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then + cat "$DOMAIN_DIR/${DOMAIN}.ec.key" "${CERT_FILE%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}_K_C.pem.ec" + copy_file_to_location "private ec key and domain cert pem" "$TEMP_DIR/${DOMAIN}_K_C.pem.ec" "${to_location}" "ec" + fi + fi + # if DOMAIN_PEM_LOCATION is not blank, then create and copy file. + if [[ -n "$DOMAIN_PEM_LOCATION" ]]; then + if [[ "$(dirname "$DOMAIN_PEM_LOCATION")" == "." ]]; then + to_location="${DOMAIN_DIR}/${DOMAIN_PEM_LOCATION}" + else + to_location="${DOMAIN_PEM_LOCATION}" + fi + cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" "$CA_CERT" > "$TEMP_DIR/${DOMAIN}.pem" + copy_file_to_location "full key, cert and chain pem" "$TEMP_DIR/${DOMAIN}.pem" "$to_location" + if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then + cat "$DOMAIN_DIR/${DOMAIN}.ec.key" "${CERT_FILE%.*}.ec.crt" "${CA_CERT%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}.pem.ec" + copy_file_to_location "full ec key, cert and chain pem" "$TEMP_DIR/${DOMAIN}.pem.ec" "${to_location}" "ec" + fi + fi + # end of copying certs. + umask "$ORIG_UMASK" +} + check_challenge_completion() { # checks with the ACME server if our challenge is OK uri=$1 domain=$2 @@ -342,11 +521,13 @@ check_challenge_completion() { # checks with the ACME server if our challenge is # if ACME response is that their check gave an invalid response, error exit if [[ "$status" == "invalid" ]] ; then err_detail=$(echo "$response" | grep "detail") + # TODO need to check for "DNS problem: SERVFAIL looking up CAA ..." and retry error_exit "$domain:Verify error:$err_detail" fi - # if ACME response is pending ( they haven't completed checks yet) then wait and try again. - if [[ "$status" == "pending" ]] ; then + # if ACME response is pending (they haven't completed checks yet) + # or valid (completed checks but not created certificate) then wait and try again. + if [[ "$status" == "pending" ]] || [[ "$status" == "valid" ]]; then info "Pending" else err_detail=$(echo "$response" | grep "detail") @@ -363,6 +544,97 @@ check_challenge_completion() { # checks with the ACME server if our challenge is fi } +check_challenge_completion_dns() { # perform validation via DNS challenge + d=${1} + rr=${2} + primary_ns=${3} + auth_key=${4} + + # check for token at public dns server, waiting for a valid response. + for ns in $primary_ns; do + info "checking DNS at $ns" + + # add +noidnout if idn-domain so search for domain in results works + if [[ "${d}" == xn--* || "${d}" == *".xn--"* ]]; then + if [[ "$DNS_CHECK_FUNC" == "nslookup" || "$DNS_CHECK_FUNC" == "host" || ("$DNS_CHECK_FUNC" == "dig" && "$DIG_SUPPORTS_NOIDNOUT" == "false") ]]; then + info "Info: idn domain but $DNS_CHECK_FUNC doesn't support +noidnout" + else + debug "adding +noidnout to DNS_CHECK_OPTIONS" + DNS_CHECK_OPTIONS="$DNS_CHECK_OPTIONS +noidnout" + fi + fi + + ntries=0 + check_dns="fail" + while [[ "$check_dns" == "fail" ]]; do + if [[ "$os" == "cygwin" ]]; then + check_result=$(nslookup -type=txt "${rr}" "${ns}" \ + | grep ^_acme -A2\ + | grep '"'|awk -F'"' '{ print $2}') + elif [[ "$DNS_CHECK_FUNC" == "drill" ]] || [[ "$DNS_CHECK_FUNC" == "dig" ]]; then + # shellcheck disable=SC2086 + debug "$DNS_CHECK_FUNC" $DNS_CHECK_OPTIONS TXT "${rr}" "@${ns}" + # shellcheck disable=SC2086 + check_result=$($DNS_CHECK_FUNC $DNS_CHECK_OPTIONS TXT "${rr}" "@${ns}" \ + | grep -i "^${rr}" \ + | grep 'IN\WTXT'|awk -F'"' '{ print $2}') + debug "check_result=\"$check_result\"" + if [[ -z "$check_result" ]]; then + # shellcheck disable=SC2086 + debug "$DNS_CHECK_FUNC" $DNS_CHECK_OPTIONS ANY "${rr}" "@${ns}" + # shellcheck disable=SC2086 + check_result=$($DNS_CHECK_FUNC $DNS_CHECK_OPTIONS ANY "${rr}" "@${ns}" \ + | grep -i "^${rr}" \ + | grep 'IN\WTXT'|awk -F'"' '{ print $2}') + debug "check_result=\"$check_result\"" + fi + elif [[ "$DNS_CHECK_FUNC" == "host" ]]; then + check_result=$($DNS_CHECK_FUNC -t TXT "${rr}" "${ns}" \ + | grep 'descriptive text'|awk -F'"' '{ print $2}') + else + check_result=$(nslookup -type=txt "${rr}" "${ns}" \ + | grep 'text ='|awk -F'"' '{ print $2}') + if [[ -z "$check_result" ]]; then + check_result=$(nslookup -type=any "${rr}" "${ns}" \ + | grep 'text ='|awk -F'"' '{ print $2}') + fi + fi + debug "expecting \"$auth_key\"" + debug "${ns} gave ... \"$check_result\"" + + if [[ "$check_result" == *"$auth_key"* ]]; then + check_dns="success" + else + if [[ $ntries -lt $DNS_WAIT_COUNT ]]; then + ntries=$(( ntries + 1 )) + + if [[ $DNS_WAIT_RETRY_ADD == "true" && $(( ntries % 10 )) == 0 ]]; then + debug "Deleting DNS via command: ${DNS_DEL_COMMAND}" + del_dns_rr "${d}" "${auth_key}" + debug "Retrying adding DNS via command: ${DNS_ADD_COMMAND}" + add_dns_rr "${d}" "${auth_key}" \ + || error_exit "DNS_ADD_COMMAND failed for domain ${d}" + fi + info "checking DNS at ${ns} for ${rr}. Attempt $ntries/${DNS_WAIT_COUNT} gave wrong result, "\ + "waiting $DNS_WAIT secs before checking again" + sleep $DNS_WAIT + else + debug "dns check failed - removing existing value" + del_dns_rr "${d}" "${auth_key}" + + error_exit "checking \"${rr}\" gave \"$check_result\" not \"$auth_key\"" + fi + fi + done + done + + if [[ "$DNS_EXTRA_WAIT" -gt 0 && "$PREVIOUSLY_VALIDATED" != "true" ]]; then + info "sleeping $DNS_EXTRA_WAIT seconds before asking the ACME server to check the dns" + sleep "$DNS_EXTRA_WAIT" + fi +} +# end of ... perform validation if via DNS challenge + check_config() { # check the config files for all obvious errors debug "checking config" @@ -382,7 +654,7 @@ check_config() { # check the config files for all obvious errors rsa|prime256v1|secp384r1|secp521r1) debug "checked PRIVATE_KEY_ALG " ;; *) - info "${DOMAIN}: invalid PRIVATE_KEY_ALG - $PRIVATE_KEY_ALG" + info "${DOMAIN}: invalid PRIVATE_KEY_ALG - '$PRIVATE_KEY_ALG'" config_errors=true ;; esac if [[ "$DUAL_RSA_ECDSA" == "true" ]] && [[ "$PRIVATE_KEY_ALG" == "rsa" ]]; then @@ -390,13 +662,13 @@ check_config() { # check the config files for all obvious errors config_errors=true fi - # get all domains + # get all domains into an array if [[ "$IGNORE_DIRECTORY_DOMAIN" == "true" ]]; then - alldomains=${SANS//,/ } + read -r -a alldomains <<< "${SANS//[, ]/ }" else - alldomains=$(echo "$DOMAIN,$SANS" | sed "s/,/ /g") + read -r -a alldomains <<< "$(echo "$DOMAIN,$SANS" | sed "s/,/ /g")" fi - if [[ -z "$alldomains" ]]; then + if [[ -z "${alldomains[*]}" ]]; then info "${DOMAIN}: no domains specified" config_errors=true fi @@ -413,12 +685,16 @@ check_config() { # check the config files for all obvious errors fi dn=0 - tmplist=$(mktemp 2>/dev/null || mktemp -t getssl) - for d in $alldomains; do # loop over domains (dn is domain number) + tmplist=$(mktemp 2>/dev/null || mktemp -t getssl.XXXXXX) || error_exit "mktemp failed" + for d in "${alldomains[@]}"; do # loop over domains (dn is domain number) debug "checking domain $d" + if [[ "$(grep "^${d}$" "$tmplist")" = "$d" ]]; then info "${DOMAIN}: $d appears to be duplicated in domain, SAN list" config_errors=true + elif [[ "$d" != "${d##\*.}" ]] && [[ "$VALIDATE_VIA_DNS" != "true" ]]; then + info "${DOMAIN}: cannot use http-01 validation for wildcard domains" + config_errors=true else echo "$d" >> "$tmplist" fi @@ -434,39 +710,53 @@ check_config() { # check the config files for all obvious errors info "${DOMAIN}: ACL location not specified for domain $d in $DOMAIN_DIR/getssl.cfg" config_errors=true fi - # check domain exists - if [[ "$DNS_CHECK_FUNC" == "drill" ]]; then - if [[ "$($DNS_CHECK_FUNC "${d}" |grep -c "${d}")" -ge 1 ]]; then - debug "found IP for ${d}" - else - info "${DOMAIN}: DNS lookup failed for ${d}" - config_errors=true + + # check domain exists using all DNS utilities. DNS_CHECK_OPTIONS may bind IP address or provide TSIG + + found_ip=false + if [[ -n "$HAS_DIG_OR_DRILL" ]]; then + # add +noidnout if idn-domain so search for domain in results works + DIG_CHECK_OPTIONS="$DNS_CHECK_OPTIONS" + if [[ ("${d}" == xn--* || "${d}" == *".xn--"* ) && "$DIG_SUPPORTS_NOIDNOUT" == "true" ]]; then + DIG_CHECK_OPTIONS="$DNS_CHECK_OPTIONS +noidnout" fi - elif [[ "$DNS_CHECK_FUNC" == "dig" ]]; then - if [[ "$($DNS_CHECK_FUNC "${d}" -t SOA|grep -c "^${d}")" -ge 1 ]]; then - debug "found SOA IP for ${d}" - elif [[ "$($DNS_CHECK_FUNC "${d}" -t A|grep -c "^${d}")" -ge 1 ]]; then - debug "found A IP for ${d}" - else - info "${DOMAIN}: DNS lookup failed for ${d}" - config_errors=true + + debug "DNS lookup using $HAS_DIG_OR_DRILL $DIG_CHECK_OPTIONS ${d}" + # shellcheck disable=SC2086 + if [[ "$($HAS_DIG_OR_DRILL $DIG_CHECK_OPTIONS -t SOA "${d}" |grep -c -i "^${d}")" -ge 1 ]]; then + found_ip=true + elif [[ "$($HAS_DIG_OR_DRILL $DIG_CHECK_OPTIONS -t A "${d}"|grep -c -i "^${d}")" -ge 1 ]]; then + found_ip=true + elif [[ "$($HAS_DIG_OR_DRILL $DIG_CHECK_OPTIONS -t AAAA "${d}"|grep -c -i "^${d}")" -ge 1 ]]; then + found_ip=true fi - elif [[ "$DNS_CHECK_FUNC" == "host" ]]; then - if [[ "$($DNS_CHECK_FUNC "${d}" |grep -c "^${d}")" -ge 1 ]]; then - debug "found IP for ${d}" - else - info "${DOMAIN}: DNS lookup failed for ${d}" - config_errors=true + fi + + if [[ "$HAS_HOST" == "true" ]]; then + debug "DNS lookup using host $DNS_CHECK_OPTIONS ${d}" + # shellcheck disable=SC2086 + if [[ "$(host $DNS_CHECK_OPTIONS "${d}" |grep -c -i "^${d}")" -ge 1 ]]; then + found_ip=true fi - elif [[ "$(nslookup -query=AAAA "${d}"|grep -c "^${d}.*has AAAA address")" -ge 1 ]]; then - debug "found IPv6 record for ${d}" - elif [[ "$(nslookup "${d}"| grep -c ^Name)" -ge 1 ]]; then - debug "found IPv4 record for ${d}" - else + fi + + if [[ "$HAS_NSLOOKUP" == "true" ]]; then + debug "DNS lookup using nslookup $DNS_CHECK_OPTIONS -query AAAA ${d}" + # shellcheck disable=SC2086 + if [[ "$(nslookup $DNS_CHECK_OPTIONS -query=AAAA "${d}"|grep -c -i "^${d}.*has AAAA address")" -ge 1 ]]; then + debug "found IPv6 record for ${d}" + found_ip=true + elif [[ "$(nslookup $DNS_CHECK_OPTIONS "${d}"| grep -c ^Name)" -ge 1 ]]; then + debug "found IPv4 record for ${d}" + found_ip=true + fi + fi + + if [[ "$found_ip" == "false" ]]; then info "${DOMAIN}: DNS lookup failed for $d" config_errors=true fi - fi # end using http-01 challenge + fi # end using dns-01 challenge ((dn++)) done @@ -479,62 +769,151 @@ check_config() { # check the config files for all obvious errors debug "${DOMAIN}: check_config completed - all OK" } -check_getssl_upgrade() { # check if a more recent version of code is available available - TEMP_UPGRADE_FILE="$(mktemp 2>/dev/null || mktemp -t getssl)" - curl --user-agent "$CURL_USERAGENT" --silent "$CODE_LOCATION" --output "$TEMP_UPGRADE_FILE" +check_getssl_upgrade() { # check if a more recent release is available + # Check GitHub for latest stable release, or a specified tag + if [[ -n "$_UPGRADE_TO_TAG" ]]; then + RELEASE_API="$RELEASE_API/tags/$_UPGRADE_TO_TAG" + fi + local release_data release_tag release_ver local_ver release_desc NEWCMD + debug "Checking for releases at $RELEASE_API" + # shellcheck disable=SC2086 + release_data="$(curl ${_NOMETER:---silent} --user-agent "$CURL_USERAGENT" -H 'Accept: application/vnd.github.v3+json' "$RELEASE_API")" errcode=$? if [[ $errcode -eq 60 ]]; then error_exit "curl needs updating, your version does not support SNI (multiple SSL domains on a single IP)" elif [[ $errcode -gt 0 ]]; then - error_exit "curl error : $errcode" + error_exit "curl error checking releases: $errcode" + fi + # Replace error in release description with _error (which is ignored by check_output_for_errors() in the tests) + debug "${release_data//error/_error}" + # awk from https://stackoverflow.com/questions/1761341/awk-print-next-record-following-matched-record + release_tag=$(awk -F'"' '/tag_name/ {f=NR} f&&NR-1==f' RS=":|," <<<"${release_data}" | sed -e's/"//g') + if [[ "${release_tag:0:1}" != 'v' ]] ; then + if [[ ${_MUTE} -eq 0 ]]; then + info "The current repository has no releases or is improperly tagged; can't check for upgrades: '$release_tag'" + fi + return 0 fi - latestversion=$(awk -F '"' '$1 == "VERSION=" {print $2}' "$TEMP_UPGRADE_FILE") - latestvdec=$(echo "$latestversion"| tr -d '.') - localvdec=$(echo "$VERSION"| tr -d '.' ) + release_ver="$( tr -d '.v' <<<"${release_tag}")" + local_ver="$( tr -d '.' <<<"${VERSION}")" debug "current code is version ${VERSION}" - debug "Most recent version is ${latestversion}" - # use a default of 0 for cases where the latest code has not been obtained. - if [[ "${latestvdec:-0}" -gt "$localvdec" ]]; then - if [[ ${_UPGRADE} -eq 1 ]]; then - install "$0" "${0}.v${VERSION}" - install -m 700 "$TEMP_UPGRADE_FILE" "$0" - if [[ ${_MUTE} -eq 0 ]]; then - echo "Updated getssl from v${VERSION} to v${latestversion}" - echo "these update notification can be turned off using the -Q option" - echo "" - echo "Updates are;" - awk "/\(${VERSION}\)$/ {s=1} s; /\(${latestversion}\)$/ {s=0}" "$TEMP_UPGRADE_FILE" | awk '{if(NR>1)print}' - echo "" - fi - if [[ -n "$_KEEP_VERSIONS" ]] && [[ "$_KEEP_VERSIONS" =~ ^[0-9]+$ ]]; then - # Obtain all locally stored old versions in getssl_versions - declare -a getssl_versions - shopt -s nullglob - for getssl_version in "$0".v*; do - getssl_versions[${#getssl_versions[@]}]="$getssl_version" - done - shopt -u nullglob - # Explicitly sort the getssl_versions array to make sure - shopt -s -o noglob - # shellcheck disable=SC2207 - IFS=$'\n' getssl_versions=($(sort <<< "${getssl_versions[*]}")) - shopt -u -o noglob - # Remove entries until given number of old versions to keep is reached - while [[ ${#getssl_versions[@]} -gt $_KEEP_VERSIONS ]]; do - debug "removing old version ${getssl_versions[0]}" - rm "${getssl_versions[0]}" - getssl_versions=("${getssl_versions[@]:1}") - done - fi - eval "$ORIGCMD" - graceful_exit - else + debug "Most recent version is ${release_tag:1}" + if [[ -z "$_UPGRADE_TO_TAG" ]] ; then + if [[ "$local_ver" -ge "$release_ver" ]] ; then return 0; fi + else + if [[ "$local_ver" -eq "$release_ver" ]] ; then return 0; fi + fi + if [[ ${_UPGRADE} -ne 1 ]]; then + if [[ ${_MUTE} -eq 0 ]]; then + release_desc="$(sed -e'/^"body": *"/!d;s/^"body": *"\([^""]*\).*$/\1/;s/\\r/\r/g;s/\\n/\n/g' <<<"$release_data")" info "" - info "A more recent version (v${latestversion}) of getssl is available, please update" - info "the easiest way is to use the -u or --upgrade flag" + info "A more recent version (${release_tag}) than $VERSION of getssl is available, please update" + info "The easiest way is to use the -u or --upgrade flag" info "" + info "Release ${release_tag} summary" + # Replace error in release description with _error (which is ignored by check_output_for_errors() in the tests) + info "${release_desc//error/_error}" + info "" + fi + return 0; + fi + + # Download the latest tag + TEMP_UPGRADE_FILE="$(mktemp 2>/dev/null || mktemp -t getssl.XXXXXX)" + if [ "$TEMP_UPGRADE_FILE" == "" ]; then + error_exit "mktemp failed" + fi + CODE_LOCATION=$(sed -e"s/master/${release_tag}/" <<<"$CODE_LOCATION") + # shellcheck disable=SC2086 + debug curl ${_NOMETER:---silent} --user-agent "$CURL_USERAGENT" "$CODE_LOCATION" --output "$TEMP_UPGRADE_FILE" + # shellcheck disable=SC2086 + status=$(curl ${_NOMETER:---silent} -w "%{http_code}" --user-agent "$CURL_USERAGENT" "$CODE_LOCATION" --output "$TEMP_UPGRADE_FILE") + errcode=$? +debug errcode=$errcode + + if [[ $errcode -eq 60 ]]; then + error_exit "curl needs updating, your version does not support SNI (multiple SSL domains on a single IP)" + elif [[ $errcode -gt 0 ]]; then + error_exit "curl error downloading release: $errcode" + fi + + if [[ $status -ne 200 ]]; then + error_exit "curl didn't find the updated version of getssl at $CODE_LOCATION" + fi + + if ! install "$0" "${0}.v${VERSION}"; then + error_exit "problem renaming old version while updating, check permissions" + fi + if ! install -m 700 "$TEMP_UPGRADE_FILE" "$0"; then + error_exit "problem installing new version while updating, check permissions" + fi + + check=$(bash "$0" -U -v) + release_tag_upper=$(echo "$release_tag" | tr "[:lower:]" "[:upper:]") + if [[ "$check" != "getssl ${release_tag_upper}" ]]; then + info "problem running new version, rolling back to old version" + if ! install "${0}.v${VERSION}" "$0"; then + error_exit "problem rolling back, you'll need to manually check $0 and $0.${VERSION}" + fi + error_exit "problem calling new version; output of $TEMP_UPGRADE_FILE -v was \"$check\", expected \"getssl ${release_tag_upper}\"" + fi + + + if [[ ${_MUTE} -eq 0 ]]; then + echo "Updated getssl from v${VERSION} to ${release_tag}" + echo "The old version remains as ${0}.v${VERSION} and should be removed" + echo "These update notifications can be turned off using the -Q option" + echo "" + echo "Updates are:" + awk "/\(${VERSION}\)$/ {s=1} s; /\(${release_tag}\)$/ || /^# ----/ {s=0}" "$TEMP_UPGRADE_FILE" | awk '{if(NR>1)print}' + echo "" + fi + + # Delete old versions, but not the version just upgraded (which can't be removed since disappearing can confuse bash) + declare -a getssl_versions + shopt -s nullglob + for getssl_version in "$0".v*; do + if [[ "$getssl_version" != "${0}.v${VERSION}" ]] ; then + getssl_versions[${#getssl_versions[@]}]="$getssl_version" fi + done + shopt -u nullglob + if [[ -n "${getssl_versions[*]}" ]] ; then + rm "${getssl_versions[@]}" + fi + + # Inhibit check for upgrades when running the new version + NEWCMD="$(sed -e's/ -\(u\|-upgrade\|U\|-nocheck\)//g;s/^\([^ ]* \)/\1--nocheck /' <<<"$ORIGCMD")" + clean_up + if [[ ${_MUTE} -eq 0 ]]; then + info "Installed $release_tag, restarting with $NEWCMD" + fi + if ! eval "$NEWCMD"; then + error_exit "Running upgraded getssl failed" fi + + graceful_exit +} + +check_version() { # true if version string $1 >= $2 + local v1 v2 i n1 n2 n + # $1 and $2 can be different lengths, but all parts must be numeric + if [[ "$1" == "$2" ]] ; then return 0; fi + local IFS='.' + # shellcheck disable=SC2206 + v1=($1) + # shellcheck disable=SC2206 + v2=($2) + n1="${#v1[@]}" + n2="${#v2[@]}" + if [[ "$n1" -ge "$n2" ]] ; then n="$n1" ; else n="$n2" ; fi + for ((i=0; i/dev/null || mktemp -t getssl) + tmp_conf=$(mktemp 2>/dev/null || mktemp -t getssl) || error_exit "mktemp failed" cat "$SSLCONF" > "$tmp_conf" printf "[SAN]\n%s" "$SANLIST" >> "$tmp_conf" # add OCSP Must-Staple to the domain csr @@ -737,7 +1154,7 @@ create_key() { # create a domain key (if it doesn't already exist) create_order() { dstring="[" - for d in $alldomains; do + for d in "${alldomains[@]}"; do dstring="${dstring}{\"type\":\"dns\",\"value\":\"$d\"}," done dstring="${dstring::${#dstring}-1}]" @@ -749,13 +1166,43 @@ create_order() { OrderLink=$(echo "$responseHeaders" | grep -i location | awk '{print $2}'| tr -d '\r\n ') debug "Order link $OrderLink" FinalizeLink=$(json_get "$response" "finalize") - dn=0 - for d in $alldomains; do - # get authorizations link - AuthLink[$dn]=$(json_get "$response" "identifiers" "value" "$d" "authorizations" "x") - debug "authorizations link for $d - ${AuthLink[$dn]}" - ((dn++)) - done + debug "Finalize link $FinalizeLink" + + if [[ $API -eq 1 ]]; then + dn=0 + for d in "${alldomains[@]}"; do + # get authorizations link + AuthLink[$dn]=$(json_get "$response" "identifiers" "value" "${d##\*.}" "authorizations" "x") + debug "authorizations link for $d - ${AuthLink[$dn]}" + ((dn++)) + done + else + # Authorization links are unsorted, so fetch the authorization link, find the domain, save response in the correct array position + AuthLinks=$(json_get "$response" "authorizations") + AuthLinkResponse=() + AuthLinkResponseHeader=() + for l in $AuthLinks; do + debug "Requesting authorizations link for $l" + send_signed_request "$l" "" + # Get domain from response + authdomain=$(json_get "$response" "identifier" "value") + wildcard=$(json_get "$response" "wildcard") + debug wildcard="$wildcard" + # find array position (This is O(n2) but doubt that we'll see performance issues) + dn=0 + for d in "${alldomains[@]}"; do + # Convert domain to lowercase as response from server will be in lowercase + lower_d=$(echo "$d" | tr "[:upper:]" "[:lower:]") + if [[ ( "$lower_d" == "$authdomain" && -z "$wildcard" ) || ( "$lower_d" == "*.${authdomain}" && -n "$wildcard" ) ]]; then + debug "Saving authorization response for $authdomain for domain alldomains[$dn]" + debug "Response = ${response//[$'\t\r\n']}" + AuthLinkResponse[$dn]=$response + AuthLinkResponseHeader[$dn]=$responseHeaders + fi + ((dn++)) + done + done + fi } date_epoc() { # convert the date into epoch time @@ -789,20 +1236,96 @@ date_renew() { # calculates the renewal time in epoch debug() { # write out debug info if the debug flag has been set if [[ ${_USE_DEBUG} -eq 1 ]]; then - echo " " - echo "$@" + # If running tests then verbose output (for debugging tests) + if [[ -n ${BATS_RUN_TMPDIR} ]]; then + echo "$(date "+%b %d %T") ${FUNCNAME[1]}:${BASH_LINENO[1]}" "$@" + else + echo " " + echo "$@" + fi fi } error_exit() { # give error message on error exit echo -e "${PROGNAME}: ${1:-"Unknown Error"}" >&2 + if [[ ${_USE_DEBUG} -eq 1 ]]; then + traceback + fi clean_up exit 1 } +find_dns_utils() { + HAS_NSLOOKUP=false + HAS_DIG_OR_DRILL="" + DIG_SUPPORTS_NOIDNOUT=false + HAS_HOST=false + if [[ -n "$(command -v nslookup 2>/dev/null)" ]]; then + debug "HAS NSLOOKUP=true" + HAS_NSLOOKUP=true + fi + + if [[ -n "$(command -v drill 2>/dev/null)" ]]; then + HAS_DIG_OR_DRILL="drill" + elif [[ -n "$(command -v dig 2>/dev/null)" ]] && dig >/dev/null 2>&1; then + if dig -r >/dev/null 2>&1; then + # use dig -r so ~/.digrc is not used + HAS_DIG_OR_DRILL="dig -r" + else + HAS_DIG_OR_DRILL="dig" + fi + fi + + if [[ -n "$HAS_DIG_OR_DRILL" ]]; then + if $HAS_DIG_OR_DRILL +noidnout >/dev/null 2>&1; then + DIG_SUPPORTS_NOIDNOUT=true + fi + + debug "HAS DIG_OR_DRILL=$HAS_DIG_OR_DRILL" + debug "DIG_SUPPORTS_NOIDNOUT=$DIG_SUPPORTS_NOIDNOUT" + fi + + if [[ -n "$(command -v host 2>/dev/null)" ]]; then + debug "HAS HOST=true" + HAS_HOST=true + fi +} + +find_ftp_command() { + FTP_COMMAND="" + if [[ -n "$(command -v ftp 2>/dev/null)" ]]; then + debug "Has ftp" + FTP_COMMAND="ftp -n" + elif [[ -n "$(command -v lftp 2>/dev/null)" ]]; then + debug "Has lftp" + FTP_COMMAND="lftp" + fi +} + + +add_dns_rr() { + d=${1} + auth_key=${2} + + # shellcheck disable=SC2018,SC2019 + lower_d=$(printf '%s' "${d#\*.}" | tr 'A-Z' 'a-z') + debug "adding DNS RR via command: ${DNS_ADD_COMMAND} ${lower_d} ${auth_key}" + eval "${DNS_ADD_COMMAND}" "${lower_d}" "${auth_key}" +} + +del_dns_rr() { + d=${1} + auth_key=${2} + + # shellcheck disable=SC2018,SC2019 + lower_d=$(printf '%s' "${d#\*.}" | tr 'A-Z' 'a-z') + debug "removing DNS RR via command: ${DNS_DEL_COMMAND} ${lower_d} ${auth_key}" + eval "${DNS_DEL_COMMAND}" "${lower_d}" "${auth_key}" +} + fulfill_challenges() { dn=0 -for d in $alldomains; do +for d in "${alldomains[@]}"; do # $d is domain in current loop, which is number $dn for ACL info "Verifying $d" if [[ "$USE_SINGLE_ACL" == "true" ]]; then @@ -813,7 +1336,7 @@ for d in $alldomains; do # request a challenge token from ACME server if [[ $API -eq 1 ]]; then - request="{\"resource\":\"new-authz\",\"identifier\":{\"type\":\"dns\",\"value\":\"$d\"}}" + request="{\"resource\":\"new-authz\",\"identifier\":{\"type\":\"dns\",\"value\":\"${d##\*.}\"}}" send_signed_request "$URL_new_authz" "$request" debug "completed send_signed_request" @@ -822,7 +1345,9 @@ for d in $alldomains; do error_exit "new-authz error: $response" fi else - send_signed_request "${AuthLink[$dn]}" "" + response=${AuthLinkResponse[$dn]} + responseHeaders=${AuthLinkResponseHeader[$dn]} + response_status=$(json_get "$response" status) fi if [[ $response_status == "valid" ]]; then @@ -840,16 +1365,14 @@ for d in $alldomains; do if [[ $VALIDATE_VIA_DNS == "true" ]]; then # set up the correct DNS token for verification if [[ $API -eq 1 ]]; then # get the dns component of the ACME response - # get the token from the dns component + # get the token and uri from the dns component token=$(json_get "$response" "token" "dns-01") - # get the uri from the dns component uri=$(json_get "$response" "uri" "dns-01") debug uri "$uri" else # APIv2 - debug "authlink response = $response" - # get the token from the http-01 component + debug "authlink response = ${response//[$'\t\r\n']}" + # get the token and uri from the dns-01 component token=$(json_get "$response" "challenges" "type" "dns-01" "token") - # get the uri from the http component uri=$(json_get "$response" "challenges" "type" "dns-01" "url") debug uri "$uri" fi @@ -858,40 +1381,45 @@ for d in $alldomains; do debug keyauthorization "$keyauthorization" #create signed authorization key from token. - auth_key=$(printf '%s' "$keyauthorization" | openssl dgst -sha256 -binary \ - | openssl base64 -e \ - | tr -d '\n\r' \ - | sed -e 's:=*$::g' -e 'y:+/:-_:') + auth_key=$(printf '%s' "$keyauthorization" \ + | openssl dgst -sha256 -binary \ + | openssl base64 -e \ + | tr -d '\n\r' \ + | sed -e 's:=*$::g' -e 'y:+/:-_:') debug auth_key "$auth_key" - debug "adding dns via command: $DNS_ADD_COMMAND $d $auth_key" - if ! eval "$DNS_ADD_COMMAND" "$d" "$auth_key" ; then - error_exit "DNS_ADD_COMMAND failed for domain $d" - fi + add_dns_rr "${d}" "${auth_key}" \ + || error_exit "DNS_ADD_COMMAND failed for domain $d" + + # shellcheck disable=SC2018,SC2019 + rr="_acme-challenge.$(printf '%s' "${d#\*.}" | tr 'A-Z' 'a-z')" # find a primary / authoritative DNS server for the domain if [[ -z "$AUTH_DNS_SERVER" ]]; then - get_auth_dns "$d" + # Find authorative dns server for _acme-challenge.{domain} (for CNAMES/acme-dns) + get_auth_dns "${rr}" + if test -n "${cname}"; then + rr=${cname} + fi + + # If no authorative dns server found, try again for {domain} + if [[ -z "$primary_ns" ]]; then + get_auth_dns "$d" + fi + elif [[ "$CHECK_PUBLIC_DNS_SERVER" == "true" ]]; then + primary_ns="$AUTH_DNS_SERVER $PUBLIC_DNS_SERVER" else primary_ns="$AUTH_DNS_SERVER" fi - debug primary_ns "$primary_ns" + debug set primary_ns = "$primary_ns" - # make a directory to hold pending dns-challenges - if [[ ! -d "$TEMP_DIR/dns_verify" ]]; then - mkdir "$TEMP_DIR/dns_verify" - fi + # internal check + check_challenge_completion_dns "${d}" "${rr}" "${primary_ns}" "${auth_key}" - # generate a file with the current variables for the dns-challenge - cat > "$TEMP_DIR/dns_verify/$d" <<- _EOF_ - token="${token}" - uri="${uri}" - keyauthorization="${keyauthorization}" - d="${d}" - primary_ns="${primary_ns}" - auth_key="${auth_key}" - _EOF_ + # let Let's Encrypt check + check_challenge_completion "${uri}" "${d}" "${keyauthorization}" + del_dns_rr "${d}" "${auth_key}" else # set up the correct http token for verification if [[ $API -eq 1 ]]; then # get the token from the http component @@ -900,8 +1428,7 @@ for d in $alldomains; do uri=$(json_get "$response" "uri" "http-01") debug uri "$uri" else # APIv2 - send_signed_request "${AuthLink[$dn]}" "" - debug "authlink response = $response" + debug "authlink response = ${response//[$'\t\r\n']}" # get the token from the http-01 component token=$(json_get "$response" "challenges" "type" "http-01" "token") # get the uri from the http component @@ -935,7 +1462,11 @@ for d in $alldomains; do else sleep "$HTTP_TOKEN_CHECK_WAIT" # check that we can reach the challenge ourselves, if not, then error - if [[ ! "$(curl --user-agent "$CURL_USERAGENT" -k --silent --location "$wellknown_url")" == "$keyauthorization" ]]; then + # ACME only allows port 80 (http), but redirects may use https. --insecure is used in case + # those certificates are being renewed. Let's Encrypt does the same. In this case, we verify + # that the correct data is returned, so this is safe. + # shellcheck disable=SC2086 + if [[ ! "$(curl ${_NOMETER} --user-agent "$CURL_USERAGENT" --insecure --silent --location "$wellknown_url")" == "$keyauthorization" ]]; then error_exit "for some reason could not reach $wellknown_url - please check it manually" fi fi @@ -948,11 +1479,10 @@ for d in $alldomains; do if [[ "${t_loc:0:4}" == "ssh:" ]] ; then sshhost=$(echo "${t_loc}"| awk -F: '{print $2}') command="rm -f ${t_loc:(( ${#sshhost} + 5))}/${token:?}" - debug "running following command to remove token" - debug "ssh $SSH_OPTS $sshhost ${command}" - # shellcheck disable=SC2029 - # shellcheck disable=SC2086 - ssh $SSH_OPTS "$sshhost" "${command}" 1>/dev/null 2>&1 + debug "running following command to remove token:" + debug "ssh $SSH_OPTS $sshhost ${command} $_REDIRECT_OUTPUT" + # shellcheck disable=SC2029 disable=SC2086 + ssh $SSH_OPTS "$sshhost" "${command}" $_REDIRECT_OUTPUT rm -f "${TEMP_DIR:?}/${token:?}" elif [[ "${t_loc:0:4}" == "ftp:" ]] ; then debug "using ftp to remove token file" @@ -960,8 +1490,8 @@ for d in $alldomains; do ftppass=$(echo "${t_loc}"| awk -F: '{print $3}') ftphost=$(echo "${t_loc}"| awk -F: '{print $4}') ftplocn=$(echo "${t_loc}"| awk -F: '{print $5}') - debug "ftp user=$ftpuser - pass=$ftppass - host=$ftphost location=$ftplocn" - ftp -n <<- EOF + debug "$FTP_COMMAND user=$ftpuser - pass=$ftppass - host=$ftphost location=$ftplocn" + $FTP_COMMAND <<- EOF open $ftphost user $ftpuser $ftppass cd $ftplocn @@ -976,87 +1506,17 @@ for d in $alldomains; do ((dn++)) fi done # end of ... loop through domains for cert ( from SANS list) - -# perform validation if via DNS challenge -if [[ $VALIDATE_VIA_DNS == "true" ]]; then - # loop through dns-variable files to check if dns has been changed - for dnsfile in "$TEMP_DIR"/dns_verify/*; do - if [[ -e "$dnsfile" ]]; then - debug "loading DNSfile: $dnsfile" - # shellcheck source=/dev/null - . "$dnsfile" - - # check for token at public dns server, waiting for a valid response. - for ns in $primary_ns; do - debug "checking dns at $ns" - ntries=0 - check_dns="fail" - while [[ "$check_dns" == "fail" ]]; do - if [[ "$os" == "cygwin" ]]; then - check_result=$(nslookup -type=txt "_acme-challenge.${d}" "${ns}" \ - | grep ^_acme -A2\ - | grep '"'|awk -F'"' '{ print $2}') - elif [[ "$DNS_CHECK_FUNC" == "drill" ]] || [[ "$DNS_CHECK_FUNC" == "dig" ]]; then - check_result=$($DNS_CHECK_FUNC TXT "_acme-challenge.${d}" "@${ns}" \ - | grep 'IN TXT'|awk -F'"' '{ print $2}') - elif [[ "$DNS_CHECK_FUNC" == "host" ]]; then - check_result=$($DNS_CHECK_FUNC -t TXT "_acme-challenge.${d}" "${ns}" \ - | grep 'descriptive text'|awk -F'"' '{ print $2}') - else - check_result=$(nslookup -type=txt "_acme-challenge.${d}" "${ns}" \ - | grep 'text ='|awk -F'"' '{ print $2}') - fi - debug "expecting $auth_key" - debug "${ns} gave ... $check_result" - - if [[ "$check_result" == *"$auth_key"* ]]; then - check_dns="success" - else - if [[ $ntries -lt 100 ]]; then - ntries=$(( ntries + 1 )) - info "checking DNS at ${ns} for ${d}. Attempt $ntries/100 gave wrong result, "\ - "waiting $DNS_WAIT secs before checking again" - sleep $DNS_WAIT - else - debug "dns check failed - removing existing value" - error_exit "checking _acme-challenge.${d} gave $check_result not $auth_key" - fi - fi - done - done - fi - done - - if [[ "$DNS_EXTRA_WAIT" -gt 0 && "$PREVIOUSLY_VALIDATED" != "true" ]]; then - info "sleeping $DNS_EXTRA_WAIT seconds before asking the ACME-server to check the dns" - sleep "$DNS_EXTRA_WAIT" - fi - - # loop through dns-variable files to let the ACME server check the challenges - for dnsfile in "$TEMP_DIR"/dns_verify/*; do - if [[ -e "$dnsfile" ]]; then - debug "loading DNSfile: $dnsfile" - # shellcheck source=/dev/null - . "$dnsfile" - - check_challenge_completion "$uri" "$d" "$keyauthorization" - - debug "remove DNS entry" - eval "$DNS_DEL_COMMAND" "$d" "$auth_key" - # remove $dnsfile after each loop. - rm -f "$dnsfile" - fi - done -fi -# end of ... perform validation if via DNS challenge -#end of varify each domain. +#end of verify each domain. } get_auth_dns() { # get the authoritative dns server for a domain (sets primary_ns ) - gad_d="$1" # domain name - gad_s="$PUBLIC_DNS_SERVER" # start with PUBLIC_DNS_SERVER + orig_gad_d="$1" # domain name + orig_gad_s="$PUBLIC_DNS_SERVER" # start with PUBLIC_DNS_SERVER + gad_d="$orig_gad_d" + gad_s="$orig_gad_s" if [[ "$os" == "cygwin" ]]; then + # shellcheck disable=SC2086 all_auth_dns_servers=$(nslookup -type=soa "${d}" ${PUBLIC_DNS_SERVER} 2>/dev/null \ | grep "primary name server" \ | awk '{print $NF}') @@ -1064,103 +1524,188 @@ get_auth_dns() { # get the authoritative dns server for a domain (sets primary_n error_exit "couldn't find primary DNS server - please set AUTH_DNS_SERVER in config" fi primary_ns="$all_auth_dns_servers" + if [[ "$CHECK_PUBLIC_DNS_SERVER" == "true" ]]; then + primary_ns="$primary_ns $PUBLIC_DNS_SERVER" + fi + return fi - if [[ "$DNS_CHECK_FUNC" == "drill" ]] || [[ "$DNS_CHECK_FUNC" == "dig" ]]; then - if [[ -z "$gad_s" ]]; then #checking for CNAMEs - res=$($DNS_CHECK_FUNC CNAME "$gad_d"| grep "^$gad_d") + if [[ -n "$HAS_DIG_OR_DRILL" ]]; then + if [[ -n "$gad_s" ]]; then + gad_s="@$gad_s" + fi + + # Two options here; either dig CNAME will return the CNAME and the NS or just the CNAME + debug "Using $HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS CNAME $gad_d $gad_s" + # shellcheck disable=SC2086 + res=$($HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS CNAME "$gad_d" $gad_s| grep "^$gad_d") + cname=$(echo "$res"| awk '$4 ~ "CNAME" {print $5}' |sed 's/\.$//g') + + if [[ $_TEST_SKIP_CNAME_CALL == 0 ]]; then + debug Checking if CNAME result contains NS records + # shellcheck disable=SC2086 + res=$($HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS CNAME "$gad_d" $gad_s| grep -E "IN\W(NS|SOA)\W") else - res=$($DNS_CHECK_FUNC CNAME "$gad_d" "@$gad_s"| grep "^$gad_d") + res= fi - if [[ -n "$res" ]]; then # domain is a CNAME so get main domain - gad_d=$(echo "$res"| awk '{print $5}' |sed 's/\.$//g') + + if [[ -n "${cname}" ]]; then + # domain is a CNAME: resolve it and continue with that + debug Domain is a CNAME, actual domain is "$cname" + gad_d=${cname} fi - if [[ -z "$gad_s" ]]; then #checking for CNAMEs - res=$($DNS_CHECK_FUNC NS "$gad_d"| grep "^$gad_d") - else - res=$($DNS_CHECK_FUNC NS "$gad_d" "@$gad_s"| grep "^$gad_d") + + # Use SOA +trace to find the name server + if [[ -z "$res" ]] && [[ $_TEST_SKIP_SOA_CALL == 0 ]]; then + # shellcheck disable=SC2086 + if [[ "$HAS_DIG_OR_DRILL" == "drill" ]]; then + debug Using "$HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS -T SOA $gad_d $gad_s" to find primary nameserver + res=$($HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS -T SOA "$gad_d" $gad_s 2>/dev/null | grep "IN\WNS\W") + else + debug Using "$HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS SOA +trace +nocomments $gad_d $gad_s" to find primary nameserver + res=$($HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS SOA +trace +nocomments "$gad_d" $gad_s 2>/dev/null | grep "IN\WNS\W") + fi fi + + # Query for NS records if [[ -z "$res" ]]; then - error_exit "couldn't find primary DNS server - please set AUTH_DNS_SERVER in config" - else - all_auth_dns_servers=$(echo "$res" | awk '$4 ~ "NS" {print $5}' | sed 's/\.$//g'|tr '\n' ' ') + debug Using "$HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS NS $gad_d $gad_s" to find primary nameserver + # shellcheck disable=SC2086 + res=$($HAS_DIG_OR_DRILL $DNS_CHECK_OPTIONS NS "$gad_d" $gad_s | grep -E "IN\W(NS|SOA)\W") fi - if [[ $CHECK_ALL_AUTH_DNS == "true" ]]; then - primary_ns="$all_auth_dns_servers" - else - primary_ns=$(echo "$all_auth_dns_servers" | awk '{print $1}') + + if [[ -n "$res" ]]; then + # Convert dig output into an array of nameservers + IFS=$'\n' read -r -d '' -a ns_servers < <(echo "$res" | awk '$4 ~ "(NS|SOA)" {print $5}' | sed 's/\.$//g') + + # Nameservers from SOA +trace includes root and all intermediate servers, so just use all the ones with the same domain as the last name server + # i.e. if we have root, google, duckdns1, duckdns2 then return all the duckdns servers + ns_domain=${ns_servers[${#ns_servers[@]} -1 ]#*.} + all_auth_dns_servers="" + for i in "${ns_servers[@]}"; do + if [[ $i =~ $ns_domain ]]; then + all_auth_dns_servers="$all_auth_dns_servers $i" + fi + done + + if [[ $CHECK_ALL_AUTH_DNS == "true" ]]; then + primary_ns="$all_auth_dns_servers" + else + primary_ns=$(echo "$all_auth_dns_servers" | awk '{print " " $1}') + fi + + if [[ "$CHECK_PUBLIC_DNS_SERVER" == "true" ]]; then + primary_ns="$primary_ns $PUBLIC_DNS_SERVER" + fi + + debug set primary_ns ="$primary_ns" + + return fi - return fi - if [[ "$DNS_CHECK_FUNC" == "host" ]]; then + # Remove leading '@' if we tried using dig/drill + gad_s="$orig_gad_s" + + if [[ "$HAS_HOST" == "true" ]]; then + gad_d="$orig_gad_d" + debug Using "host -t NS" to find primary name server for "$gad_d" + # shellcheck disable=SC2086 if [[ -z "$gad_s" ]]; then - res=$($DNS_CHECK_FUNC -t NS "$gad_d"| grep "name server") + res=$(host $DNS_CHECK_OPTIONS -t NS "$gad_d"| grep "name server") else - res=$($DNS_CHECK_FUNC -t NS "$gad_d" "$gad_s"| grep "name server") + res=$(host $DNS_CHECK_OPTIONS -t NS "$gad_d" $gad_s| grep "name server") fi - if [[ -z "$res" ]]; then - error_exit "couldn't find primary DNS server - please set AUTH_DNS_SERVER in config" - else + if [[ -n "$res" ]]; then all_auth_dns_servers=$(echo "$res" | awk '{print $4}' | sed 's/\.$//g'|tr '\n' ' ') + if [[ $CHECK_ALL_AUTH_DNS == "true" ]]; then + primary_ns="$all_auth_dns_servers" + else + primary_ns=$(echo "$all_auth_dns_servers" | awk '{print $1}') + fi + + if [[ "$CHECK_PUBLIC_DNS_SERVER" == "true" ]]; then + primary_ns="$primary_ns $PUBLIC_DNS_SERVER" + fi + + return fi - if [[ $CHECK_ALL_AUTH_DNS == "true" ]]; then - primary_ns="$all_auth_dns_servers" - else - primary_ns=$(echo "$all_auth_dns_servers" | awk '{print $1}') - fi - return fi - res=$(nslookup -debug -type=soa -type=ns "$gad_d" ${gad_s}) + if [[ "$HAS_NSLOOKUP" == "true" ]]; then + gad_d="$orig_gad_d" + debug Using "nslookup $DNS_CHECK_OPTIONS -debug -type=soa -type=ns $gad_d $gad_s" to find primary name server + # shellcheck disable=SC2086 + res=$(nslookup $DNS_CHECK_OPTIONS -debug -type=soa -type=ns "$gad_d" ${gad_s}) + + if [[ "$(echo "$res" | grep -c "Non-authoritative")" -gt 0 ]]; then + # this is a Non-authoritative server, need to check for an authoritative one. + gad_s=$(echo "$res" | awk '$2 ~ "nameserver" {print $4; exit }' |sed 's/\.$//g') + if [[ "$(echo "$res" | grep -c "an't find")" -gt 0 ]]; then + # if domain name doesn't exist, then find auth servers for next level up + gad_s=$(echo "$res" | awk '$1 ~ "origin" {print $3; exit }') + gad_d=$(echo "$res" | awk '$1 ~ "->" {print $2; exit}') + # handle scenario where awk returns nothing + if [[ -z "$gad_d" ]]; then + gad_d="$orig_gad_d" + fi + fi - if [[ "$(echo "$res" | grep -c "Non-authoritative")" -gt 0 ]]; then - # this is a Non-authoritative server, need to check for an authoritative one. - gad_s=$(echo "$res" | awk '$2 ~ "nameserver" {print $4; exit }' |sed 's/\.$//g') - if [[ "$(echo "$res" | grep -c "an't find")" -gt 0 ]]; then - # if domain name doesn't exist, then find auth servers for next level up - gad_s=$(echo "$res" | awk '$1 ~ "origin" {print $3; exit }') - gad_d=$(echo "$res" | awk '$1 ~ "->" {print $2; exit}') + # shellcheck disable=SC2086 + res=$(nslookup $DNS_CHECK_OPTIONS -debug -type=soa -type=ns "$gad_d" ${gad_s}) fi - fi - if [[ -z "$gad_s" ]]; then - res=$(nslookup -debug -type=soa -type=ns "$gad_d") - else - res=$(nslookup -debug -type=soa -type=ns "$gad_d" "${gad_s}") - fi + if [[ "$(echo "$res" | grep -c "canonical name")" -gt 0 ]]; then + gad_d=$(echo "$res" | awk ' $2 ~ "canonical" {print $5; exit }' |sed 's/\.$//g') + elif [[ "$(echo "$res" | grep -c "an't find")" -gt 0 ]]; then + gad_s=$(echo "$res" | awk ' $1 ~ "origin" {print $3; exit }') + gad_d=$(echo "$res"| awk '$1 ~ "->" {print $2; exit}') + # handle scenario where awk returns nothing + if [[ -z "$gad_d" ]]; then + gad_d="$orig_gad_d" + fi + fi - if [[ "$(echo "$res" | grep -c "canonical name")" -gt 0 ]]; then - gad_d=$(echo "$res" | awk ' $2 ~ "canonical" {print $5; exit }' |sed 's/\.$//g') - elif [[ "$(echo "$res" | grep -c "an't find")" -gt 0 ]]; then - gad_s=$(echo "$res" | awk ' $1 ~ "origin" {print $3; exit }') - gad_d=$(echo "$res"| awk '$1 ~ "->" {print $2; exit}') - fi + # shellcheck disable=SC2086 + # not quoting gad_s fixes the nslookup: couldn't get address for '': not found warning (#332) + all_auth_dns_servers=$(nslookup $DNS_CHECK_OPTIONS -debug -type=soa -type=ns "$gad_d" $gad_s \ + | awk '$1 ~ "nameserver" {print $3}' \ + | sed 's/\.$//g'| tr '\n' ' ') - all_auth_dns_servers=$(nslookup -type=soa -type=ns "$gad_d" "$gad_s" \ - | awk ' $2 ~ "nameserver" {print $4}' \ - | sed 's/\.$//g'| tr '\n' ' ') - if [[ $CHECK_ALL_AUTH_DNS == "true" ]]; then - primary_ns="$all_auth_dns_servers" - else - primary_ns=$(echo "$all_auth_dns_servers" | awk '{print $1}') + if [[ -n "$all_auth_dns_servers" ]]; then + if [[ $CHECK_ALL_AUTH_DNS == "true" ]]; then + primary_ns="$all_auth_dns_servers" + else + primary_ns=$(echo "$all_auth_dns_servers" | awk '{print $1}') + fi + + return + fi fi + + # nslookup on alpine/ubuntu containers doesn't support -debug, print a warning in this case + # This means getssl cannot check that the DNS record has been updated on the primary name server + info "Warning: Couldn't find primary DNS server - please set PUBLIC_DNS_SERVER or AUTH_DNS_SERVER in config" + info "This means getssl cannot check the DNS entry has been updated" } get_certificate() { # get certificate for csr, if all domains validated. gc_csr=$1 # the csr file gc_certfile=$2 # The filename for the certificate gc_cafile=$3 # The filename for the CA certificate + gc_fullchain=$4 # The filename for the fullchain der=$(openssl req -in "$gc_csr" -outform DER | urlbase64) + if [[ $API -eq 1 ]]; then send_signed_request "$URL_new_cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" "needbase64" # convert certificate information into correct format and save to file. CertData=$(awk ' $1 ~ "^Location" {print $2}' "$CURL_HEADER" |tr -d '\r') if [[ "$CertData" ]] ; then echo -----BEGIN CERTIFICATE----- > "$gc_certfile" - curl --user-agent "$CURL_USERAGENT" --silent "$CertData" | openssl base64 -e >> "$gc_certfile" + # shellcheck disable=SC2086 + curl ${_NOMETER} --user-agent "$CURL_USERAGENT" --silent "$CertData" | openssl base64 -e >> "$gc_certfile" echo -----END CERTIFICATE----- >> "$gc_certfile" info "Certificate saved in $CERT_FILE" fi @@ -1180,7 +1725,8 @@ get_certificate() { # get certificate for csr, if all domains validated. | sed 's/>//g') if [[ "$IssuerData" ]] ; then echo -----BEGIN CERTIFICATE----- > "$gc_cafile" - curl --user-agent "$CURL_USERAGENT" --silent "$IssuerData" | openssl base64 -e >> "$gc_cafile" + # shellcheck disable=SC2086 + curl ${_NOMETER} --user-agent "$CURL_USERAGENT" --silent "$IssuerData" | openssl base64 -e >> "$gc_cafile" echo -----END CERTIFICATE----- >> "$gc_cafile" info "The intermediate CA cert is in $gc_cafile" fi @@ -1198,9 +1744,55 @@ get_certificate() { # get certificate for csr, if all domains validated. done info "Requesting certificate" CertData=$(json_get "$response" "certificate") - send_signed_request "$CertData" "" "" "$FULL_CHAIN" - info "Full certificate saved in $FULL_CHAIN" - awk -v CERT_FILE="$gc_certfile" -v CA_CERT="$gc_cafile" 'BEGIN {outfile=CERT_FILE} split_after==1 {outfile=CA_CERT;split_after=0} /-----END CERTIFICATE-----/ {split_after=1} {print > outfile}' "$FULL_CHAIN" + send_signed_request "$CertData" "" "" "$gc_fullchain" + IFS=$'\n' read -r -d '' -a alternate_links < <(echo "$responseHeaders" | grep "^Link" | grep "alternate" | awk -F"[<>]" '{print $2}') + debug "Alternate Links are ${alternate_links[*]}" + if [[ -n "$PREFERRED_CHAIN" ]]; then + cert_to_check=$(mktemp 2>/dev/null || mktemp -t getssl.XXXXXX) || error_exit "mktemp failed" + # Check the default certificate to see if that has the required chain + cp "$gc_fullchain" "$cert_to_check" + i=0 + while [[ $i -le ${#alternate_links[@]} ]]; do + cert_issuer=$(openssl crl2pkcs7 -nocrl -certfile "$cert_to_check" | openssl pkcs7 -print_certs -text -noout | grep 'Issuer:' | tail -1 | awk -F"CN=" '{ print $2 }') + debug Certificate issued by "$cert_issuer" + if [[ $cert_issuer = *${PREFERRED_CHAIN}* ]]; then + debug "Found required certificate" + cp "$cert_to_check" "$gc_fullchain" + break + fi + + if [[ $i -lt ${#alternate_links[@]} ]]; then + debug "Fetching next alternate certificate $i ${alternate_links[$i]}" + send_signed_request "${alternate_links[$i]}" "" "" "$cert_to_check" + fi + i=$(( i + 1 )) + done + + # tidy up + rm -f "$cert_to_check" + fi + + awk -v CERT_FILE="$gc_certfile" -v CA_CERT="$gc_cafile" 'BEGIN {outfile=CERT_FILE} split_after==1 {outfile=CA_CERT;split_after=0} /-----END CERTIFICATE-----/ {split_after=1} {print > outfile}' "$gc_fullchain" + if [[ "$FULL_CHAIN_INCLUDE_ROOT" = "true" ]]; then + # Some of the code below was copied from zakjan/cert-chain-resolver + + # Download the certificate for the issuer using the "CA Issuers" attribute from the AIA x509 extension + issuer_url=$(openssl x509 -inform pem -noout -text -in "$gc_certfile" | awk 'BEGIN {FS="CA Issuers - URI:"} NF==2 {print $2; exit}') + debug Issuer for "$gc_certfile" is "$issuer_url" + + # Keep downloading issuer certficates until we find the root certificate (which doesn't have a "CA Issuers" attribure) + cp "$gc_certfile" "$gc_fullchain" + while [[ -n "$issuer_url" ]]; do + debug Fetching certificate issuer from "$issuer_url" + # shellcheck disable=SC2086 + issuer_cert=$(curl ${_NOMETER} --user-agent "$CURL_USERAGENT" --silent "$issuer_url" | openssl x509 -inform der -outform pem) + debug Fetched issuer certificate "$(echo "$issuer_cert" | openssl x509 -inform pem -noout -text | awk 'BEGIN {FS="Subject: "} NF==2 {print $2; exit}')" + echo "$issuer_cert" >> "$gc_fullchain" + + # get issuer for the certificate that's just been downloaded + issuer_url=$(echo "$issuer_cert" | openssl x509 -inform pem -noout -text | awk 'BEGIN {FS="CA Issuers - URI:"} NF==2 {print $2; exit}') + done + fi info "Certificate saved in $gc_certfile" fi } @@ -1208,9 +1800,10 @@ get_certificate() { # get certificate for csr, if all domains validated. get_cr() { # get curl response url="$1" debug url "$url" - response=$(curl --user-agent "$CURL_USERAGENT" --silent "$url") + # shellcheck disable=SC2086 + response=$(curl ${_NOMETER} --user-agent "$CURL_USERAGENT" --silent "$url") ret=$? - debug response "$response" + debug response "${response//[$'\t\r\n']}" code=$(json_get "$response" status) debug code "$code" debug "get_cr return code $ret" @@ -1231,6 +1824,14 @@ get_os() { # function to get the current Operating System os="cygwin" elif [[ ${uname_res:0:5} == "MINGW" ]]; then os="mingw" + elif [[ ${uname_res} == "SunOS" ]]; then + os="solaris" + if [ -d /usr/gnu/bin ]; then + export PATH=/usr/gnu/bin:$PATH + else + echo "Path with required GNU commands not found, please install /usr/gnu/bin" + exit 1 + fi else os="unknown" fi @@ -1261,8 +1862,8 @@ get_signing_params() { # get signing parameters from key crv="$(openssl ec -in "$skey" -noout -text 2>/dev/null | awk '$2 ~ "CURVE:" {print $3}')" if [[ -z "$crv" ]]; then gsp_keytype="$(openssl ec -in "$skey" -noout -text 2>/dev/null \ - | grep "^ASN1 OID:" \ - | awk '{print $3}')" + | grep "^ASN1 OID:" \ + | awk '{print $3}')" case "$gsp_keytype" in prime256v1) crv="P-256" ;; secp384r1) crv="P-384" ;; @@ -1277,8 +1878,8 @@ get_signing_params() { # get signing parameters from key *) error_exit "invalid curve algorithm type $crv";; esac pubtext="$(openssl ec -in "$skey" -noout -text 2>/dev/null \ - | awk '/^pub:/{p=1;next}/^ASN1 OID:/{p=0}p' \ - | tr -d ": \n\r")" + | awk '/^pub:/{p=1;next}/^ASN1 OID:/{p=0}p' \ + | tr -d ": \n\r")" mid=$(( (${#pubtext} -2) / 2 + 2 )) x64=$(echo "$pubtext" | cut -b 3-$mid | hex2bin | urlbase64) y64=$(echo "$pubtext" | cut -b $((mid+1))-${#pubtext} | hex2bin | urlbase64) @@ -1291,8 +1892,10 @@ get_signing_params() { # get signing parameters from key } graceful_exit() { # normal exit function. + exit_code="${1-0}" clean_up - exit + # shellcheck disable=SC2086 + exit $exit_code } help_message() { # print out the help message @@ -1308,13 +1911,16 @@ help_message() { # print out the help message -c, --create Create default config files -f, --force Force renewal of cert (overrides expiry checks) -h, --help Display this help message and exit + -i, --install Install certificates and reload service -q, --quiet Quiet mode (only outputs on error, success of new cert, or getssl was upgraded) -Q, --mute Like -q, but also mute notification about successful upgrade -r, --revoke "cert" "key" [CA_server] Revoke a certificate (the cert and key are required) - -u, --upgrade Upgrade getssl if a more recent version is available - -k, --keep "#" Maximum number of old getssl versions to keep when upgrading + -u, --upgrade Upgrade getssl if a more recent version is available - can be used with or without domain(s) + -X, --experimental tag Upgrade to experimental releases, specified by tag (e.g. v9.43) -U, --nocheck Do not check if a more recent version is available + -v --version Display current version of $PROGNAME -w working_dir "Working directory" + --preferred-chain "chain" Use an alternate chain for the certificate _EOF_ } @@ -1326,7 +1932,12 @@ hex2bin() { # Remove spaces, add leading zero, escape as hex string ensuring no info() { # write out info as long as the quiet flag has not been set. if [[ ${_QUIET} -eq 0 ]]; then - echo "$@" + # If running tests then verbose output (for debugging tests) + if [[ -n ${BATS_RUN_TMPDIR} ]]; then + echo "$(date "+%b %d %T") ${FUNCNAME[1]}:${BASH_LINENO[1]}" "$@" + else + echo "$@" + fi fi } @@ -1534,6 +2145,51 @@ json_get() { # get values from json fi } +obtain_ca_resource_locations() +{ + CURL_RESPONSE_FILE="$(mktemp 2>/dev/null || mktemp -t getssl.XXXXXX)" + + for suffix in "" "/directory" "/dir"; + do + # Obtain CA resource locations + # shellcheck disable=SC2086 + ca_all_loc=$(curl ${_NOMETER} --user-agent "$CURL_USERAGENT" "${CA}${suffix}" 2> $CURL_RESPONSE_FILE) + errcode=$? + if [[ $errcode -ne 0 ]]; then + response=$(cat "$CURL_RESPONSE_FILE") + rm "$CURL_RESPONSE_FILE" + error_exit "ERROR curl \"$CA$suffix\" failed with $errcode and returned:\n$response" + else + rm "$CURL_RESPONSE_FILE" + fi + + debug "ca_all_loc from ${CA}${suffix} gives $ca_all_loc" + # APIv1 + URL_new_reg=$(echo "$ca_all_loc" | grep "new-reg" | awk -F'"' '{print $4}') + URL_new_authz=$(echo "$ca_all_loc" | grep "new-authz" | awk -F'"' '{print $4}') + URL_new_cert=$(echo "$ca_all_loc" | grep "new-cert" | awk -F'"' '{print $4}') + #API v2 + URL_newAccount=$(echo "$ca_all_loc" | grep "newAccount" | awk -F'"' '{print $4}') + URL_newNonce=$(echo "$ca_all_loc" | grep "newNonce" | awk -F'"' '{print $4}') + URL_newOrder=$(echo "$ca_all_loc" | grep "newOrder" | awk -F'"' '{print $4}') + URL_revoke=$(echo "$ca_all_loc" | grep "revokeCert" | awk -F'"' '{print $4}') + + if [[ -n "$URL_new_reg" ]] || [[ -n "$URL_newAccount" ]]; then + break + fi + done + + # If a directory offers both versions, select V2. + if [[ -n "$URL_newAccount" ]]; then + API=2 + elif [[ -n "$URL_new_reg" ]]; then + API=1 + else + error_exit "unknown API version" + fi + debug "Using API v$API" +} + os_esed() { # Use different sed version for different os types (extended regex) if [[ "$os" == "bsd" ]]; then # BSD requires -E flag for extended regex sed -E "${@}" @@ -1572,22 +2228,25 @@ purge_archive() { # purge archive of old, invalid, certificates reload_service() { # Runs a command to reload services ( via ssh if needed) if [[ -n "$RELOAD_CMD" ]]; then info "reloading SSL services" - if [[ "${RELOAD_CMD:0:4}" == "ssh:" ]] ; then - sshhost=$(echo "$RELOAD_CMD"| awk -F: '{print $2}') - command=${RELOAD_CMD:(( ${#sshhost} + 5))} - debug "running following command to reload cert" - debug "ssh $SSH_OPTS $sshhost ${command}" - # shellcheck disable=SC2029 - # shellcheck disable=SC2086 - ssh $SSH_OPTS "$sshhost" "${command}" 1>/dev/null 2>&1 - # allow 2 seconds for services to restart - sleep 2 - else - debug "running reload command $RELOAD_CMD" - if ! eval "$RELOAD_CMD" ; then - error_exit "error running $RELOAD_CMD" + for ARELOAD_CMD in "${RELOAD_CMD[@]}" + do + if [[ "${ARELOAD_CMD:0:4}" == "ssh:" ]] ; then + sshhost=$(echo "$ARELOAD_CMD"| awk -F: '{print $2}') + command=${ARELOAD_CMD:(( ${#sshhost} + 5))} + debug "running following command to reload cert:" + debug "ssh $SSH_OPTS $sshhost ${command} $_REDIRECT_OUTPUT" + # shellcheck disable=SC2029 + # shellcheck disable=SC2086 + ssh $SSH_OPTS "$sshhost" "${command}" $_REDIRECT_OUTPUT + # allow 2 seconds for services to restart + sleep 2 + else + debug "running reload command: $ARELOAD_CMD" + if ! eval "$ARELOAD_CMD" ; then + error_exit "error running: $ARELOAD_CMD" + fi fi - fi + done fi } @@ -1597,10 +2256,10 @@ revoke_certificate() { # revoke a certificate ACCOUNT_KEY="$REVOKE_KEY" # need to set the revoke key as "account_key" since it's used in send_signed_request. get_signing_params "$REVOKE_KEY" - TEMP_DIR=$(mktemp -d 2>/dev/null || mktemp -d -t getssl) - debug "revoking from $CA" - rcertdata=$(openssl x509 -in "$REVOKE_CERT" -inform PEM -outform DER | urlbase64) - send_signed_request "$URL_revoke" "{\"resource\": \"revoke-cert\", \"certificate\": \"$rcertdata\"}" + TEMP_DIR=$(mktemp -d 2>/dev/null || mktemp -d -t getssl) || error_exit "mktemp failed" + debug "revoking from $URL_revoke" + rcertdata=$(sed '1d;$d' "$REVOKE_CERT" | tr -d "\r\n" | tr '/+' '_-' | tr -d '= ') + send_signed_request "$URL_revoke" "{\"certificate\": \"$rcertdata\",\"reason\": $REVOKE_REASON}" if [[ $code -eq "200" ]]; then info "certificate revoked" else @@ -1609,16 +2268,18 @@ revoke_certificate() { # revoke a certificate } requires() { # check if required function is available + args=("${@}") + lastarg=${args[${#args[@]}-1]} if [[ "$#" -gt 1 ]]; then # if more than 1 value, check list for i in "$@"; do - if [[ "$i" == "${!#}" ]]; then # if on last variable then exit as not found + if [[ "$i" == "$lastarg" ]]; then # if on last variable then exit as not found error_exit "this script requires one of: ${*:1:$(($#-1))}" fi res=$(command -v "$i" 2>/dev/null) debug "checking for $i ... $res" if [[ -n "$res" ]]; then # if function found, then set variable to function and return - debug "function $i found at $res - setting ${!#} to $i" - eval "${!#}=\$i" + debug "function $i found at $res - setting ${lastarg} to $i" + eval "${lastarg}=\$i" return fi done @@ -1683,9 +2344,8 @@ send_signed_request() { # Sends a request to the ACME server, signed with your p CURL_HEADER="$TEMP_DIR/curl.header" dp="$TEMP_DIR/curl.dump" - CURL="curl " - # shellcheck disable=SC2072 - if [[ "$($CURL -V | head -1 | cut -d' ' -f2 )" > "7.33" ]]; then + CURL="curl ${_NOMETER} " + if check_version "${_CURL_VERSION}" "7.33" ; then CURL="$CURL --http1.1 " fi @@ -1701,7 +2361,7 @@ send_signed_request() { # Sends a request to the ACME server, signed with your p # get nonce from ACME server if [[ $API -eq 1 ]]; then nonceurl="$CA/directory" - nonce=$($CURL -I $nonceurl | grep "^Replay-Nonce:" | awk '{print $2}' | tr -d '\r\n ') + nonce=$($CURL -I "$nonceurl" | grep "^Replay-Nonce:" | awk '{print $2}' | tr -d '\r\n ') else # APIv2 nonce=$($CURL -I "$URL_newNonce" | grep "^Replay-Nonce:" | awk '{print $2}' | tr -d '\r\n ') fi @@ -1750,15 +2410,18 @@ send_signed_request() { # Sends a request to the ACME server, signed with your p while [[ "$code" -eq 500 ]]; do if [[ "$outfile" ]] ; then $CURL -X POST -H "Content-Type: application/jose+json" --data "$body" "$url" > "$outfile" + errcode=$? response=$(cat "$outfile") elif [[ "$needbase64" ]] ; then response=$($CURL -X POST -H "Content-Type: application/jose+json" --data "$body" "$url" | urlbase64) + errcode=$? else response=$($CURL -X POST -H "Content-Type: application/jose+json" --data "$body" "$url") + errcode=$? fi - if [[ "$response" == "" ]]; then - error_exit "ERROR curl \"$url\" returned nothing" + if [[ $errcode -gt 0 || ( "$response" == "" && $url != *"revoke"* ) ]]; then + error_exit "ERROR curl \"$url\" failed with $errcode and returned \"$response\"" fi responseHeaders=$(cat "$CURL_HEADER") @@ -1768,7 +2431,7 @@ send_signed_request() { # Sends a request to the ACME server, signed with your p fi debug responseHeaders "$responseHeaders" - debug response "$response" + debug response "${response//[$'\t\r\n']}" code=$(awk ' $1 ~ "^HTTP" {print $2}' "$CURL_HEADER" | tail -1) debug code "$code" if [[ "$code" == 4* && $response != *"error:badNonce"* && "$code" != 409 ]]; then @@ -1791,7 +2454,7 @@ send_signed_request() { # Sends a request to the ACME server, signed with your p fi debug "response status = $response_status" if [[ "$code" -eq 500 ]]; then - info "error on acme server - trying again ...." + info "_error on acme server - trying again ...." debug "loop_limit = $loop_limit" sleep 5 loop_limit=$((loop_limit - 1)) @@ -1857,6 +2520,17 @@ signal_exit() { # Handle trapped signals esac } +traceback() { # Print function traceback + local i d=1 lbl=" called" + debug "Traceback" + for ((i=$((${#FUNCNAME[@]}-1)); i>0; i--)); do + if [[ ${i} -eq 1 ]] ; then lbl=" called traceback" ; fi + debug "$(printf "%*s%s() line %d%s\n" "$d" '' "${FUNCNAME[$i]}" "${BASH_LINENO[$((i-1))]}" "$lbl")" + ((d++)) + done + return 0 +} + urlbase64() { # urlbase64: base64 encoded string with '+' replaced with '-' and '/' replaced with '_' openssl base64 -e | tr -d '\n\r' | os_esed -e 's:=*$::g' -e 'y:+/:-_:' } @@ -1875,77 +2549,100 @@ urlbase64_decode() { usage() { # echos out the program usage echo "Usage: $PROGNAME [-h|--help] [-d|--debug] [-c|--create] [-f|--force] [-a|--all] [-q|--quiet]"\ - "[-Q|--mute] [-u|--upgrade] [-k|--keep #] [-U|--nocheck] [-r|--revoke cert key] [-w working_dir] domain" + "[-Q|--mute] [-u|--upgrade] [-X|--experimental tag] [-U|--nocheck] [-r|--revoke cert key] [-w working_dir]"\ + "[--preferred-chain chain] domain" } write_domain_template() { # write out a template file for a domain. - cat > "$1" <<- _EOF_domain_ - # vim: filetype=sh - # - # This file is read second (and per domain if running with the -a option) - # and overwrites any settings from the first file - # - # Uncomment and modify any variables you need - # see https://github.com/srvrco/getssl/wiki/Config-variables for details - # see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs - # - # The staging server is best for testing - #CA="https://acme-staging-v02.api.letsencrypt.org/" - # This server issues full certificates, however has rate limits - #CA="https://acme-v02.api.letsencrypt.org" - - # Private key types - can be rsa, prime256v1, secp384r1 or secp521r1 - #PRIVATE_KEY_ALG="rsa" - - # Additional domains - this could be multiple domains / subdomains in a comma separated list - # Note: this is Additional domains - so should not include the primary domain. - SANS="${EX_SANS}" - - # Acme Challenge Location. The first line for the domain, the following ones for each additional domain. - # If these start with ssh: then the next variable is assumed to be the hostname and the rest the location. - # An ssh key will be needed to provide you with access to the remote server. - # Optionally, you can specify a different userid for ssh/scp to use on the remote server before the @ sign. - # If left blank, the username on the local server will be used to authenticate against the remote server. - # If these start with ftp: then the next variables are ftpuserid:ftppassword:servername:ACL_location - # These should be of the form "/path/to/your/website/folder/.well-known/acme-challenge" - # where "/path/to/your/website/folder/" is the path, on your web server, to the web root for your domain. - # You can also user WebDAV over HTTPS as transport mechanism. To do so, start with davs: followed by username, - # password, host, port (explicitly needed even if using default port 443) and path on the server. - #ACL=('/var/www/${DOMAIN}/web/.well-known/acme-challenge' - # 'ssh:server5:/var/www/${DOMAIN}/web/.well-known/acme-challenge' - # 'ssh:sshuserid@server5:/var/www/${DOMAIN}/web/.well-known/acme-challenge' - # 'ftp:ftpuserid:ftppassword:${DOMAIN}:/web/.well-known/acme-challenge' - # 'davs:davsuserid:davspassword:{DOMAIN}:443:/web/.well-known/acme-challenge') - - # Specify SSH options, e.g. non standard port in SSH_OPTS - # (Can also use SCP_OPTS and SFTP_OPTS) - # SSH_OPTS=-p 12345 - - # Set USE_SINGLE_ACL="true" to use a single ACL for all checks - #USE_SINGLE_ACL="false" - - # Location for all your certs, these can either be on the server (full path name) - # or using ssh /sftp as for the ACL - #DOMAIN_CERT_LOCATION="/etc/ssl/${DOMAIN}.crt" # this is domain cert - #DOMAIN_KEY_LOCATION="/etc/ssl/${DOMAIN}.key" # this is domain key - #CA_CERT_LOCATION="/etc/ssl/chain.crt" # this is CA cert - #DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert - #DOMAIN_PEM_LOCATION="" # this is the domain key, domain cert and CA cert - - # The command needed to reload apache / nginx or whatever you use - #RELOAD_CMD="" - - # Uncomment the following line to prevent non-interactive renewals of certificates - #PREVENT_NON_INTERACTIVE_RENEWAL="true" - - # Define the server type. This can be https, ftp, ftpi, imap, imaps, pop3, pop3s, smtp, - # smtps_deprecated, smtps, smtp_submission, xmpp, xmpps, ldaps or a port number which - # will be checked for certificate expiry and also will be checked after - # an update to confirm correct certificate is running (if CHECK_REMOTE) is set to true - #SERVER_TYPE="https" - #CHECK_REMOTE="true" - #CHECK_REMOTE_WAIT="2" # wait 2 seconds before checking the remote server - _EOF_domain_ + if [[ -s "$WORKING_DIR/getssl_default.cfg" ]]; then + export DOMAIN="$DOMAIN" + export EX_SANS="$EX_SANS" + envsubst < "$WORKING_DIR/getssl_default.cfg" > "$1" + else + cat > "$1" <<- _EOF_domain_ + # vim: filetype=sh + # + # This file is read second (and per domain if running with the -a option) + # and overwrites any settings from the first file + # + # Uncomment and modify any variables you need + # see https://github.com/srvrco/getssl/wiki/Config-variables for details + # see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs + # + # The staging server is best for testing + #CA="https://acme-staging-v02.api.letsencrypt.org" + # This server issues full certificates, however has rate limits + #CA="https://acme-v02.api.letsencrypt.org" + + # Private key types - can be rsa, prime256v1, secp384r1 or secp521r1 + #PRIVATE_KEY_ALG="rsa" + + # Additional domains - this could be multiple domains / subdomains in a comma separated list + # Note: this is Additional domains - so should not include the primary domain. + SANS="${EX_SANS}" + + # Acme Challenge Location. The first line for the domain, the following ones for each additional domain. + # If these start with ssh: then the next variable is assumed to be the hostname and the rest the location. + # An ssh key will be needed to provide you with access to the remote server. + # Optionally, you can specify a different userid for ssh/scp to use on the remote server before the @ sign. + # If left blank, the username on the local server will be used to authenticate against the remote server. + # If these start with ftp:/ftpes:/ftps: then the next variables are ftpuserid:ftppassword:servername:ACL_location + # These should be of the form "/path/to/your/website/folder/.well-known/acme-challenge" + # where "/path/to/your/website/folder/" is the path, on your web server, to the web root for your domain. + # ftp: uses regular ftp; ftpes: ftp over explicit TLS (port 21); ftps: ftp over implicit TLS (port 990). + # ftps/ftpes support FTPS_OPTIONS, e.g. to add "--insecure" to the curl command for hosts with self-signed certificates. + # You can also user WebDAV over HTTPS as transport mechanism. To do so, start with davs: followed by username, + # password, host, port (explicitly needed even if using default port 443) and path on the server. + # Multiple locations can be defined for a file by separating the locations with a semi-colon. + #ACL=('/var/www/${DOMAIN}/web/.well-known/acme-challenge' + # 'ssh:server5:/var/www/${DOMAIN}/web/.well-known/acme-challenge' + # 'ssh:sshuserid@server5:/var/www/${DOMAIN}/web/.well-known/acme-challenge' + # 'ftp:ftpuserid:ftppassword:${DOMAIN}:/web/.well-known/acme-challenge' + # 'davs:davsuserid:davspassword:{DOMAIN}:443:/web/.well-known/acme-challenge' + # 'ftps:ftpuserid:ftppassword:${DOMAIN}:/web/.well-known/acme-challenge' + # 'ftpes:ftpuserid:ftppassword:${DOMAIN}:/web/.well-known/acme-challenge') + + # Specify SSH options, e.g. non standard port in SSH_OPTS + # (Can also use SCP_OPTS and SFTP_OPTS) + # SSH_OPTS=-p 12345 + + # Set USE_SINGLE_ACL="true" to use a single ACL for all checks + #USE_SINGLE_ACL="false" + + # Preferred Chain - use an different certificate root from the default + # This uses wildcard matching so requesting "X1" returns the correct certificate - may need to escape characters + # Staging options are: "(STAGING) Doctored Durian Root CA X3" and "(STAGING) Pretend Pear X1" + # Production options are: "ISRG Root X1" and "ISRG Root X2" + #PREFERRED_CHAIN="\(STAGING\) Pretend Pear X1" + + # Uncomment this if you need the full chain file to include the root certificate (Java keystores, Nutanix Prism) + #FULL_CHAIN_INCLUDE_ROOT="true" + + # Location for all your certs, these can either be on the server (full path name) + # or using ssh /sftp as for the ACL + #DOMAIN_CERT_LOCATION="/etc/ssl/${DOMAIN}.crt" # this is domain cert + #DOMAIN_KEY_LOCATION="/etc/ssl/${DOMAIN}.key" # this is domain key + #CA_CERT_LOCATION="/etc/ssl/chain.crt" # this is CA cert + #DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert + #DOMAIN_PEM_LOCATION="" # this is the domain key, domain cert and CA cert + + # The command needed to reload apache / nginx or whatever you use. + # Several (ssh) commands may be given using a bash array: + # RELOAD_CMD=('ssh:sshuserid@server5:systemctl reload httpd' 'logger getssl for server5 efficient.') + #RELOAD_CMD="" + + # Uncomment the following line to prevent non-interactive renewals of certificates + #PREVENT_NON_INTERACTIVE_RENEWAL="true" + + # Define the server type. This can be https, ftp, ftpi, imap, imaps, pop3, pop3s, smtp, + # smtps_deprecated, smtps, smtp_submission, xmpp, xmpps, ldaps or a port number which + # will be checked for certificate expiry and also will be checked after + # an update to confirm correct certificate is running (if CHECK_REMOTE) is set to true + #SERVER_TYPE="https" + #CHECK_REMOTE="true" + #CHECK_REMOTE_WAIT="2" # wait 2 seconds before checking the remote server + _EOF_domain_ + fi } write_getssl_template() { # write out the main template file @@ -1975,7 +2672,18 @@ write_getssl_template() { # write out the main template file PRIVATE_KEY_ALG="rsa" #REUSE_PRIVATE_KEY="true" - # The command needed to reload apache / nginx or whatever you use + # Preferred Chain - use an different certificate root from the default + # This uses wildcard matching so requesting "X1" returns the correct certificate - may need to escape characters + # Staging options are: "(STAGING) Doctored Durian Root CA X3" and "(STAGING) Pretend Pear X1" + # Production options are: "ISRG Root X1" and "ISRG Root X2" + #PREFERRED_CHAIN="\(STAGING\) Pretend Pear X1" + + # Uncomment this if you need the full chain file to include the root certificate (Java keystores, Nutanix Prism) + #FULL_CHAIN_INCLUDE_ROOT="true" + + # The command needed to reload apache / nginx or whatever you use. + # Several (ssh) commands may be given using a bash array: + # RELOAD_CMD=('ssh:sshuserid@server5:systemctl reload httpd' 'logger getssl for server5 efficient.') #RELOAD_CMD="" # The time period within which you want to allow renewal of a certificate @@ -1995,6 +2703,19 @@ write_getssl_template() { # write out the main template file #VALIDATE_VIA_DNS="true" #DNS_ADD_COMMAND= #DNS_DEL_COMMAND= + + # Unusual configurations (especially split views) may require these. + # If you have a mixture, these can go in the per-domain getssl.cfg. + # + # If you must use an external DNS Server (e.g. due to split views) + # Specify it here. Otherwise, the default is to find the zone master. + # The default will usually work. + # PUBLIC_DNS_SERVER="8.8.8.8" + + # If getssl is unable to determine the authoritative nameserver for a domain + # it will as you to enter AUTH_DNS_SERVER. This is a server that + # can answer queries for the zone - a master or a slave, not a recursive server. + # AUTH_DNS_SERVER="10.0.0.14" _EOF_getssl_ } @@ -2017,35 +2738,54 @@ while [[ -n ${1+defined} ]]; do case $1 in -h | --help) help_message; graceful_exit ;; + -v | --version) + echo "$PROGNAME V$VERSION"; graceful_exit ;; -d | --debug) - _USE_DEBUG=1 ;; + _USE_DEBUG=1 ;; -c | --create) - _CREATE_CONFIG=1 ;; + _CREATE_CONFIG=1 ;; -f | --force) - _FORCE_RENEW=1 ;; + _FORCE_RENEW=1 ;; + --notify-valid) + # Exit 2 if certificate is valid and doesn't need renewing + _NOTIFY_VALID=2 ;; -a | --all) - _CHECK_ALL=1 ;; + _CHECK_ALL=1 ;; -k | --keep) - shift; _KEEP_VERSIONS="$1";; + shift; + echo "--keep has no effect" ;; -q | --quiet) - _QUIET=1 ;; + _QUIET=1 ;; -Q | --mute) - _QUIET=1 - _MUTE=1 ;; + _QUIET=1 + _MUTE=1 ;; -r | --revoke) - _REVOKE=1 - shift - REVOKE_CERT="$1" - shift - REVOKE_KEY="$1" - shift - REVOKE_CA="$1" ;; + _REVOKE=1 + shift + REVOKE_CERT="$1" + shift + REVOKE_KEY="$1" + shift + CA="$1" + REVOKE_CA="$1" + REVOKE_REASON=0 ;; -u | --upgrade) - _UPGRADE=1 ;; + _UPGRADE=1 ;; + -X | --experimental) + _UPGRADE_TO_TAG="$1" + shift ;; -U | --nocheck) _UPGRADE_CHECK=0 ;; + -i | --install) + _CERT_INSTALL=1 ;; + --check-config) + _ONLY_CHECK_CONFIG=1 ;; -w) shift; WORKING_DIR="$1" ;; + -preferred-chain | --preferred-chain) + shift; PREFERRED_CHAIN="$1" ;; + --source) + return ;; -*) usage error_exit "Unknown option $1" ;; @@ -2061,6 +2801,11 @@ while [[ -n ${1+defined} ]]; do shift done +if [[ ${_USE_DEBUG} -eq 1 ]]; then + # Do not hide outputs when debug mode is on + _REDIRECT_OUTPUT="" +fi + # Main logic ############ @@ -2078,6 +2823,7 @@ requires which requires openssl requires curl requires dig nslookup drill host DNS_CHECK_FUNC +requires dirname requires awk requires tr requires date @@ -2086,9 +2832,37 @@ requires sed requires sort requires mktemp +# Make sure cURL doesn't display a progress meter (if it's new enough) +# --silent also does this, but suppresses warnings and informational messages too. +# TODO: see where --silent can be removed (if _NOMETER defaults to --silent for old versions?) +# This would help with debugging transfer errors. + +_CURL_VERSION="$(curl -V | head -1 | cut -d' ' -f2 )" +if check_version "${_CURL_VERSION}" "7.67" ; then + _NOMETER="--no-progress-meter" +fi + +# Make sure mktemp works before going too far +MKDIR_TEST_FILE="$(mktemp 2>/dev/null || mktemp -t getssl.XXXXXX)" +if [ "$MKDIR_TEST_FILE" == "" ]; then + error_exit "mktemp failed" +else + rm "$MKDIR_TEST_FILE" +fi +unset MKDIR_TEST_FILE + # Check if upgrades are available (unless they have specified -U to ignore Upgrade checks) if [[ $_UPGRADE_CHECK -eq 1 ]]; then check_getssl_upgrade + # if nothing in command line and no revocation and not only config check, + # then exit after upgrade + if [[ -z "$DOMAIN" ]] && [[ ${_CHECK_ALL} -ne 1 ]] && [[ ${_REVOKE} -ne 1 ]] && [ "${_ONLY_CHECK_CONFIG}" -ne 1 ]; then + # if nothing in command line, print help before exit. + if [[ -z "$DOMAIN" ]] && [[ ${_CHECK_ALL} -ne 1 ]]; then + help_message + fi + graceful_exit + fi fi # Revoke a certificate if requested @@ -2101,7 +2875,8 @@ if [[ $_REVOKE -eq 1 ]]; then else CA=$REVOKE_CA fi - URL_revoke=$(curl --user-agent "$CURL_USERAGENT" "${CA}/directory" 2>/dev/null | grep "revoke-cert" | awk -F'"' '{print $4}') + + obtain_ca_resource_locations revoke_certificate graceful_exit fi @@ -2115,6 +2890,19 @@ if [[ -z "$DOMAIN" ]] && [[ ${_CHECK_ALL} -ne 1 ]]; then graceful_exit fi +# Test working directory candidates if unset. Last candidate defaults (~/getssl/) +if [[ -z "${WORKING_DIR}" ]] +then + for WORKING_DIR in "${WORKING_DIR_CANDIDATES[@]}" + do + debug "Testing working dir location '${WORKING_DIR}'" + if [[ -s "$WORKING_DIR/getssl.cfg" ]] + then + break + fi + done +fi + # if the "working directory" doesn't exist, then create it. if [[ ! -d "$WORKING_DIR" ]]; then debug "Making working directory - $WORKING_DIR" @@ -2128,6 +2916,12 @@ if [[ -s "$WORKING_DIR/getssl.cfg" ]]; then . "$WORKING_DIR/getssl.cfg" fi +if [[ -n "$DNS_CHECK_FUNC" ]]; then + requires "${DNS_CHECK_FUNC}" +else + requires nslookup drill dig host DNS_CHECK_FUNC +fi + # Define defaults for variables not set in the main config. ACCOUNT_KEY="${ACCOUNT_KEY:=$WORKING_DIR/account.key}" DOMAIN_STORAGE="${DOMAIN_STORAGE:=$WORKING_DIR}" @@ -2171,7 +2965,7 @@ if [[ ${_CHECK_ALL} -eq 1 ]]; then fi # check if $dir is a directory with a getssl.cfg in it if [[ -f "$dir/getssl.cfg" ]]; then - cmd="$cmd -w $WORKING_DIR $(basename "$dir")" + cmd="$cmd -w $WORKING_DIR \"$(basename "$dir")\"" debug "CMD: $cmd" eval "$cmd" fi @@ -2204,16 +2998,21 @@ if [[ ${_CREATE_CONFIG} -eq 1 ]]; then info "creating domain config file in $DOMAIN_DIR/getssl.cfg" # if domain has an existing cert, copy from domain and use to create defaults. EX_CERT=$(echo \ - | openssl s_client -servername "${DOMAIN}" -connect "${DOMAIN}:443" 2>/dev/null \ + | openssl s_client -servername "${DOMAIN##\*.}" -connect "${DOMAIN##\*.}:443" 2>/dev/null \ | openssl x509 2>/dev/null) - EX_SANS="www.${DOMAIN}" + EX_SANS="www.${DOMAIN##\*.}" if [[ -n "${EX_CERT}" ]]; then + escaped_d=${DOMAIN/\*/\\\*} EX_SANS=$(echo "$EX_CERT" \ | openssl x509 -noout -text 2>/dev/null| grep "Subject Alternative Name" -A2 \ - | grep -Eo "DNS:[a-zA-Z 0-9.-]*" | sed "s@DNS:$DOMAIN@@g" | grep -v '^$' | cut -c 5-) + | grep -Eo "DNS:[a-zA-Z 0-9.\*-]*" | sed "s@DNS:${escaped_d}@@g" | grep -v '^$' | cut -c 5-) EX_SANS=${EX_SANS//$'\n'/','} fi + if [[ -n "${EX_SANS}" ]]; then + info "Adding SANS=$EX_SANS from certificate installed on ${DOMAIN##\*.} to new configuration file" + fi write_domain_template "$DOMAIN_DIR/getssl.cfg" + info "created domain config file in $DOMAIN_DIR/getssl.cfg" fi TEMP_DIR="$DOMAIN_DIR/tmp" # end of "-c|--create" option, so exit @@ -2241,51 +3040,45 @@ if [[ -s "$DOMAIN_DIR/getssl.cfg" ]]; then . "$DOMAIN_DIR/getssl.cfg" fi +# Ensure SANS is comma separated by replacing any number of commas or spaces with a single comma +# shellcheck disable=SC2001 +SANS=$(echo "$SANS" | sed 's/[, ]\+/,/g') + # from SERVER_TYPE set REMOTE_PORT and REMOTE_EXTRA set_server_type +# check what dns utils are installed +find_dns_utils + +# Find what ftp client is installed +find_ftp_command + +# auto upgrade clients to v2 +auto_upgrade_v2 + # check config for typical errors. check_config +# exit if just checking config (used for testing) +if [ "${_ONLY_CHECK_CONFIG}" -eq 1 ]; then + info "Configuration check successful" + graceful_exit +fi + +# if -i|--install install certs, reload and exit +if [ "0${_CERT_INSTALL}" -eq 1 ]; then + cert_install + reload_service + graceful_exit +fi + if [[ -e "$DOMAIN_DIR/FORCE_RENEWAL" ]]; then rm -f "$DOMAIN_DIR/FORCE_RENEWAL" || error_exit "problem deleting file $DOMAIN_DIR/FORCE_RENEWAL" _FORCE_RENEW=1 info "${DOMAIN}: forcing renewal (due to FORCE_RENEWAL file)" fi -# Obtain CA resource locations -ca_all_loc=$(curl --user-agent "$CURL_USERAGENT" "${CA}" 2>/dev/null) -debug "ca_all_loc from ${CA} gives $ca_all_loc" -# APIv1 -URL_new_reg=$(echo "$ca_all_loc" | grep "new-reg" | awk -F'"' '{print $4}') -URL_new_authz=$(echo "$ca_all_loc" | grep "new-authz" | awk -F'"' '{print $4}') -URL_new_cert=$(echo "$ca_all_loc" | grep "new-cert" | awk -F'"' '{print $4}') -#API v2 -URL_newAccount=$(echo "$ca_all_loc" | grep "newAccount" | awk -F'"' '{print $4}') -URL_newNonce=$(echo "$ca_all_loc" | grep "newNonce" | awk -F'"' '{print $4}') -URL_newOrder=$(echo "$ca_all_loc" | grep "newOrder" | awk -F'"' '{print $4}') -if [[ -z "$URL_new_reg" ]] && [[ -z "$URL_newAccount" ]]; then - ca_all_loc=$(curl --user-agent "$CURL_USERAGENT" "${CA}/directory" 2>/dev/null) - debug "ca_all_loc from ${CA}/directory gives $ca_all_loc" - # APIv1 - URL_new_reg=$(echo "$ca_all_loc" | grep "new-reg" | awk -F'"' '{print $4}') - URL_new_authz=$(echo "$ca_all_loc" | grep "new-authz" | awk -F'"' '{print $4}') - URL_new_cert=$(echo "$ca_all_loc" | grep "new-cert" | awk -F'"' '{print $4}') - #API v2 - URL_newAccount=$(echo "$ca_all_loc" | grep "newAccount" | awk -F'"' '{print $4}') - URL_newNonce=$(echo "$ca_all_loc" | grep "newNonce" | awk -F'"' '{print $4}') - URL_newOrder=$(echo "$ca_all_loc" | grep "newOrder" | awk -F'"' '{print $4}') -fi - -if [[ -n "$URL_new_reg" ]]; then - API=1 -elif [[ -n "$URL_newAccount" ]]; then - API=2 -else - info "unknown API version" - graceful_exit -fi -debug "Using API v$API" +obtain_ca_resource_locations # Check if awk supports json_awk (required for ACMEv2) if [[ $API -eq 2 ]]; then @@ -2297,10 +3090,22 @@ fi # if check_remote is true then connect and obtain the current certificate (if not forcing renewal) if [[ "${CHECK_REMOTE}" == "true" ]] && [[ $_FORCE_RENEW -eq 0 ]]; then - debug "getting certificate for $DOMAIN from remote server" + real_d=${DOMAIN##\*.} + debug "getting certificate for $DOMAIN from remote server ($real_d)" + if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then + # shellcheck disable=SC2086 + # check if openssl supports RSA-PSS + if [[ $(echo | openssl s_client -servername "${real_d}" -connect "${real_d}:${REMOTE_PORT}" ${REMOTE_EXTRA} -sigalgs RSA-PSS+SHA256 2>/dev/null) ]]; then + CIPHER="-sigalgs RSA+SHA256:RSA+SHA384:RSA+SHA512:RSA-PSS+SHA256:RSA-PSS+SHA512" + else + CIPHER="-sigalgs RSA+SHA256:RSA+SHA384:RSA+SHA512" + fi + else + CIPHER="" + fi # shellcheck disable=SC2086 EX_CERT=$(echo \ - | openssl s_client -servername "${DOMAIN}" -connect "${DOMAIN}:${REMOTE_PORT}" ${REMOTE_EXTRA} 2>/dev/null \ + | openssl s_client -servername "${real_d}" -connect "${real_d}:${REMOTE_PORT}" ${REMOTE_EXTRA} ${CIPHER} 2>/dev/null \ | openssl x509 2>/dev/null) if [[ -n "$EX_CERT" ]]; then # if obtained a cert if [[ -s "$CERT_FILE" ]]; then # if local exists @@ -2314,7 +3119,7 @@ if [[ "${CHECK_REMOTE}" == "true" ]] && [[ $_FORCE_RENEW -eq 0 ]]; then else # check if the certificate is for the right domain EX_CERT_DOMAIN=$(echo "$EX_CERT" | openssl x509 -text \ - | sed -n -e 's/^ *Subject: .* CN=\([A-Za-z0-9.-]*\).*$/\1/p; /^ *DNS:.../ { s/ *DNS://g; y/,/\n/; p; }' \ + | sed -n -e 's/^ *Subject: .*CN=\([A-Za-z0-9.-]*\).*$/\1/p; /^ *DNS:.../ { s/ *DNS://g; y/,/\n/; p; }' \ | sort -u | grep "^$DOMAIN\$") if [[ "$EX_CERT_DOMAIN" == "$DOMAIN" ]]; then # check renew-date on ex_cert and compare to local ( if local exists) @@ -2347,18 +3152,26 @@ if [[ "${CHECK_REMOTE}" == "true" ]] && [[ $_FORCE_RENEW -eq 0 ]]; then copy_file_to_location "full pem" \ "$TEMP_DIR/${DOMAIN}_chain.pem" \ "$DOMAIN_CHAIN_LOCATION" + umask 077 cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" > "$TEMP_DIR/${DOMAIN}_K_C.pem" + umask "$ORIG_UMASK" copy_file_to_location "private key and domain cert pem" \ "$TEMP_DIR/${DOMAIN}_K_C.pem" \ "$DOMAIN_KEY_CERT_LOCATION" + umask 077 cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" "$CA_CERT" > "$TEMP_DIR/${DOMAIN}.pem" + umask "$ORIG_UMASK" copy_file_to_location "full pem" \ "$TEMP_DIR/${DOMAIN}.pem" \ "$DOMAIN_PEM_LOCATION" reload_service fi else - info "${DOMAIN}: Certificate on remote domain does not match, ignoring remote certificate" + # Get the domain from the existing certificate for the error message + EX_CERT_DOMAIN=$(echo "$EX_CERT" | openssl x509 -text \ + | sed -n -e 's/^ *Subject: .*CN=\([A-Za-z0-9.-]*\).*$/\1/p; /^ *DNS:.../ { s/ *DNS://g; y/,/\n/; p; }' \ + | sort -u | head -1) + info "${DOMAIN}: Certificate on remote domain does not match, ignoring remote certificate ($EX_CERT_DOMAIN != $real_d)" fi fi else @@ -2368,23 +3181,55 @@ if [[ "${CHECK_REMOTE}" == "true" ]] && [[ $_FORCE_RENEW -eq 0 ]]; then fi # end of .... check_remote is true then connect and obtain the current certificate +#create SAN +if [[ -z "$SANS" ]]; then + SANLIST="subjectAltName=DNS:${DOMAIN}" +elif [[ "$IGNORE_DIRECTORY_DOMAIN" == "true" ]]; then + SANLIST="subjectAltName=DNS:${SANS//[, ]/,DNS:}" +else + SANLIST="subjectAltName=DNS:${DOMAIN},DNS:${SANS//[, ]/,DNS:}" +fi +debug "created SAN list = $SANLIST" + +# check if private key alg has changed from RSA to EC (or vice versa) +if [[ "$DUAL_RSA_ECDSA" == "false" ]] && [[ -s "$DOMAIN_DIR/${DOMAIN}.key" ]]; then + case "${PRIVATE_KEY_ALG}" in + rsa) + if grep -q -- "-----BEGIN EC PRIVATE KEY-----" "$DOMAIN_DIR/${DOMAIN}.key"; then + rm -f "$DOMAIN_DIR/${DOMAIN}.key" + _FORCE_RENEW=1 + fi ;; + prime256v1|secp384r1|secp521r1) + if grep -q -- "-----BEGIN RSA PRIVATE KEY-----" "$DOMAIN_DIR/${DOMAIN}.key"; then + rm -f "$DOMAIN_DIR/${DOMAIN}.key" + _FORCE_RENEW=1 + fi ;; + esac +fi + # if there is an existing certificate file, check details. if [[ -s "$CERT_FILE" ]]; then debug "certificate $CERT_FILE exists" enddate=$(openssl x509 -in "$CERT_FILE" -noout -enddate 2>/dev/null| cut -d= -f 2-) debug "local cert is valid until $enddate" + existing_sanlist=$(openssl x509 -in "$CERT_FILE" -noout -text | grep "DNS:" | sed '{ s/ *DNS://g; y/,/\n/; }' | sort -u | xargs | sed 's/ /,/g') + sorted_sanlist=$(echo "$SANLIST" | sed '{ s/subjectAltName=//; s/ *DNS://g; y/,/\n/; }' | sort -u | xargs | sed 's/ /,/g') + debug "local cert is for domains: ${existing_sanlist}" if [[ "$enddate" != "-" ]]; then enddate_s=$(date_epoc "$enddate") - if [[ $(date_renew) -lt "$enddate_s" ]] && [[ $_FORCE_RENEW -ne 1 ]]; then + if [[ $(date_renew) -lt "$enddate_s" ]] && [[ $_FORCE_RENEW -ne 1 ]] && [[ "$existing_sanlist" == "$sorted_sanlist" ]]; then issuer=$(openssl x509 -in "$CERT_FILE" -noout -issuer 2>/dev/null) if [[ "$issuer" == *"Fake LE Intermediate"* ]] && [[ "$CA" == "https://acme-v02.api.letsencrypt.org" ]]; then debug "upgrading from fake cert to real" else info "${DOMAIN}: certificate is valid for more than $RENEW_ALLOW days (until $enddate)" - # everything is OK, so exit. - graceful_exit + # everything is OK, so exit, if requested with the --notify-valid, exit with code 2 + graceful_exit $_NOTIFY_VALID fi else + if [[ "$existing_sanlist" != "$sorted_sanlist" ]]; then + info "Domain list in existing certificate ($existing_sanlist) does not match domains requested ($sorted_sanlist), so recreating certificate" + fi debug "${DOMAIN}: certificate needs renewal" fi fi @@ -2408,12 +3253,13 @@ fi # if not reusing private key, then remove the old keys if [[ "$REUSE_PRIVATE_KEY" != "true" ]]; then if [[ -s "$DOMAIN_DIR/${DOMAIN}.key" ]]; then - rm -f "$DOMAIN_DIR/${DOMAIN}.key" + rm -f "$DOMAIN_DIR/${DOMAIN}.key" fi if [[ -s "$DOMAIN_DIR/${DOMAIN}.ec.key" ]]; then - rm -f "$DOMAIN_DIR/${DOMAIN}.ec.key" + rm -f "$DOMAIN_DIR/${DOMAIN}.ec.key" fi fi + # create new domain keys if they don't already exist if [[ "$DUAL_RSA_ECDSA" == "false" ]]; then create_key "${PRIVATE_KEY_ALG}" "$DOMAIN_DIR/${DOMAIN}.key" "$DOMAIN_KEY_LENGTH" @@ -2423,16 +3269,6 @@ else fi # End of creating domain keys. -#create SAN -if [[ -z "$SANS" ]]; then - SANLIST="subjectAltName=DNS:${DOMAIN}" -elif [[ "$IGNORE_DIRECTORY_DOMAIN" == "true" ]]; then - SANLIST="subjectAltName=DNS:${SANS//,/,DNS:}" -else - SANLIST="subjectAltName=DNS:${DOMAIN},DNS:${SANS//,/,DNS:}" -fi -debug "created SAN list = $SANLIST" - #create CSR's if [[ "$DUAL_RSA_ECDSA" == "false" ]]; then create_csr "$DOMAIN_DIR/${DOMAIN}.csr" "$DOMAIN_DIR/${DOMAIN}.key" @@ -2489,9 +3325,9 @@ info "Verify each domain" # loop through domains for cert ( from SANS list) if [[ "$IGNORE_DIRECTORY_DOMAIN" == "true" ]]; then - alldomains=${SANS//,/ } + read -r -a alldomains <<< "${SANS//[, ]/ }" else - alldomains=$(echo "$DOMAIN,$SANS" | sed "s/,/ /g") + read -r -a alldomains <<< "$(echo "$DOMAIN,$SANS" | sed "s/,/ /g")" fi if [[ $API -eq 2 ]]; then @@ -2506,7 +3342,8 @@ info "Verification completed, obtaining certificate." #obtain the certificate. get_certificate "$DOMAIN_DIR/${DOMAIN}.csr" \ "$CERT_FILE" \ - "$CA_CERT" + "$CA_CERT" \ + "$FULL_CHAIN" if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then info "Creating order for EC certificate" if [[ $API -eq 2 ]]; then @@ -2516,7 +3353,8 @@ if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then info "obtaining EC certificate." get_certificate "$DOMAIN_DIR/${DOMAIN}.ec.csr" \ "${CERT_FILE%.*}.ec.crt" \ - "${CA_CERT%.*}.ec.crt" + "${CA_CERT%.*}.ec.crt" \ + "${FULL_CHAIN%.*}.ec.crt" fi # create Archive of new certs and keys. @@ -2525,76 +3363,8 @@ cert_archive debug "Certificates obtained and archived locally, will now copy to specified locations" # copy certs to the correct location (creating concatenated files as required) -umask 077 +cert_install -copy_file_to_location "domain certificate" "$CERT_FILE" "$DOMAIN_CERT_LOCATION" -copy_file_to_location "private key" "$DOMAIN_DIR/${DOMAIN}.key" "$DOMAIN_KEY_LOCATION" -copy_file_to_location "CA certificate" "$CA_CERT" "$CA_CERT_LOCATION" -if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then - if [[ -n "$DOMAIN_CERT_LOCATION" ]]; then - copy_file_to_location "ec domain certificate" \ - "${CERT_FILE%.*}.ec.crt" \ - "${DOMAIN_CERT_LOCATION}" \ - "ec" - fi - if [[ -n "$DOMAIN_KEY_LOCATION" ]]; then - copy_file_to_location "ec private key" \ - "$DOMAIN_DIR/${DOMAIN}.ec.key" \ - "${DOMAIN_KEY_LOCATION}" \ - "ec" - fi - if [[ -n "$CA_CERT_LOCATION" ]]; then - copy_file_to_location "ec CA certificate" \ - "${CA_CERT%.*}.ec.crt" \ - "${CA_CERT_LOCATION%.*}.crt" \ - "ec" - fi -fi - -# if DOMAIN_CHAIN_LOCATION is not blank, then create and copy file. -if [[ -n "$DOMAIN_CHAIN_LOCATION" ]]; then - if [[ "$(dirname "$DOMAIN_CHAIN_LOCATION")" == "." ]]; then - to_location="${DOMAIN_DIR}/${DOMAIN_CHAIN_LOCATION}" - else - to_location="${DOMAIN_CHAIN_LOCATION}" - fi - cat "$CERT_FILE" "$CA_CERT" > "$TEMP_DIR/${DOMAIN}_chain.pem" - copy_file_to_location "full chain" "$TEMP_DIR/${DOMAIN}_chain.pem" "$to_location" - if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then - cat "${CERT_FILE%.*}.ec.crt" "${CA_CERT%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}_chain.pem.ec" - copy_file_to_location "full chain" "$TEMP_DIR/${DOMAIN}_chain.pem.ec" "${to_location}" "ec" - fi -fi -# if DOMAIN_KEY_CERT_LOCATION is not blank, then create and copy file. -if [[ -n "$DOMAIN_KEY_CERT_LOCATION" ]]; then - if [[ "$(dirname "$DOMAIN_KEY_CERT_LOCATION")" == "." ]]; then - to_location="${DOMAIN_DIR}/${DOMAIN_KEY_CERT_LOCATION}" - else - to_location="${DOMAIN_KEY_CERT_LOCATION}" - fi - cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" > "$TEMP_DIR/${DOMAIN}_K_C.pem" - copy_file_to_location "private key and domain cert pem" "$TEMP_DIR/${DOMAIN}_K_C.pem" "$to_location" - if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then - cat "$DOMAIN_DIR/${DOMAIN}.ec.key" "${CERT_FILE%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}_K_C.pem.ec" - copy_file_to_location "private ec key and domain cert pem" "$TEMP_DIR/${DOMAIN}_K_C.pem.ec" "${to_location}" "ec" - fi -fi -# if DOMAIN_PEM_LOCATION is not blank, then create and copy file. -if [[ -n "$DOMAIN_PEM_LOCATION" ]]; then - if [[ "$(dirname "$DOMAIN_PEM_LOCATION")" == "." ]]; then - to_location="${DOMAIN_DIR}/${DOMAIN_PEM_LOCATION}" - else - to_location="${DOMAIN_PEM_LOCATION}" - fi - cat "$DOMAIN_DIR/${DOMAIN}.key" "$CERT_FILE" "$CA_CERT" > "$TEMP_DIR/${DOMAIN}.pem" - copy_file_to_location "full key, cert and chain pem" "$TEMP_DIR/${DOMAIN}.pem" "$to_location" - if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then - cat "$DOMAIN_DIR/${DOMAIN}.ec.key" "${CERT_FILE%.*}.ec.crt" "${CA_CERT%.*}.ec.crt" > "$TEMP_DIR/${DOMAIN}.pem.ec" - copy_file_to_location "full ec key, cert and chain pem" "$TEMP_DIR/${DOMAIN}.pem.ec" "${to_location}" "ec" - fi -fi -# end of copying certs. -umask "$ORIG_UMASK" # Run reload command to restart apache / nginx or whatever system reload_service @@ -2619,17 +3389,46 @@ fi # Check if the certificate is installed correctly if [[ ${CHECK_REMOTE} == "true" ]]; then + real_d=${DOMAIN##\*.} sleep "$CHECK_REMOTE_WAIT" - # shellcheck disable=SC2086 - CERT_REMOTE=$(echo \ - | openssl s_client -servername "${DOMAIN}" -connect "${DOMAIN}:${REMOTE_PORT}" ${REMOTE_EXTRA} 2>/dev/null \ - | openssl x509 -noout -fingerprint 2>/dev/null) - CERT_LOCAL=$(openssl x509 -noout -fingerprint < "$CERT_FILE" 2>/dev/null) - if [[ "$CERT_LOCAL" == "$CERT_REMOTE" ]]; then - info "${DOMAIN} - certificate installed OK on server" + if [[ "$DUAL_RSA_ECDSA" == "true" ]]; then + # shellcheck disable=SC2086 + # check if openssl supports RSA-PSS + if [[ $(echo | openssl s_client -servername "${real_d}" -connect "${real_d}:${REMOTE_PORT}" ${REMOTE_EXTRA} -sigalgs RSA-PSS+SHA256 2>/dev/null) ]]; then + PARAMS=("-sigalgs RSA-PSS+SHA256:RSA-PSS+SHA512:RSA+SHA256:RSA+SHA384:RSA+SHA512" "-sigalgs ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA512") + else + PARAMS=("-sigalgs RSA+SHA256:RSA+SHA384:RSA+SHA512" "-sigalgs ECDSA+SHA256:ECDSA+SHA384:ECDSA+SHA512") + fi + + CERTS=("$CERT_FILE" "${CERT_FILE%.*}.ec.crt") + TYPES=("rsa" "$PRIVATE_KEY_ALG") else - error_exit "${DOMAIN} - certificate obtained but certificate on server is different from the new certificate" - fi + PARAMS=("") + CERTS=("$CERT_FILE") + TYPES=("$PRIVATE_KEY_ALG") + fi + + for ((i=0; i<${#PARAMS[@]};++i)); do + debug "Checking ${CERTS[i]}" + # shellcheck disable=SC2086 + debug openssl s_client -servername "${real_d}" -connect "${real_d}:${REMOTE_PORT}" ${REMOTE_EXTRA} ${PARAMS[i]} + # shellcheck disable=SC2086 + CERT_REMOTE=$(echo \ + | openssl s_client -servername "${real_d}" -connect "${real_d}:${REMOTE_PORT}" ${REMOTE_EXTRA} ${PARAMS[i]} 2>/dev/null \ + | openssl x509 -noout -fingerprint 2>/dev/null) + CERT_LOCAL=$(openssl x509 -noout -fingerprint < "${CERTS[i]}" 2>/dev/null) + debug CERT_LOCAL="${CERT_LOCAL}" + debug CERT_REMOTE="${CERT_REMOTE}" + if [[ "$CERT_LOCAL" == "$CERT_REMOTE" ]]; then + info "${real_d} - ${TYPES[i]} certificate installed OK on server" + elif [[ "$CERT_REMOTE" == "" ]]; then + info "${CERTS[i]} not returned by server" + error_exit "${real_d} - ${TYPES[i]} certificate obtained but not installed on server" + else + info "${CERTS[i]} didn't match server" + error_exit "${real_d} - ${TYPES[i]} certificate obtained but certificate on server is different from the new certificate" + fi + done fi # end of Check if the certificate is installed correctly diff --git a/other_scripts/cpanel_cert_upload b/other_scripts/cpanel_cert_upload index a76181b..757504f 100755 --- a/other_scripts/cpanel_cert_upload +++ b/other_scripts/cpanel_cert_upload @@ -14,12 +14,12 @@ rawurlencode() { local pos c o for (( pos=0 ; pos ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg + + create_certificate + assert_success + check_output_for_errors +} + + +@test "Check we can revoke a certificate (no suffix)" { + if [ -n "$STAGING" ]; then + CONFIG_FILE="getssl-dns01.cfg" + else + CONFIG_FILE="getssl-http01.cfg" + fi + echo 'CA="https://acme-staging-v02.api.letsencrypt.org"' > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg + + . "${CODE_DIR}/test/test-config/${CONFIG_FILE}" + CERT=${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt + KEY=${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.key + + run ${CODE_DIR}/getssl -U -d --revoke $CERT $KEY $CA + assert_success + check_output_for_errors +} diff --git a/test/16-test-bad-acl.bats b/test/16-test-bad-acl.bats new file mode 100644 index 0000000..8c7bc9c --- /dev/null +++ b/test/16-test-bad-acl.bats @@ -0,0 +1,28 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt +} + + +@test "Test behaviour if ACL= line has a space" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + CONFIG_FILE="getssl-http01-bad-acl.cfg" + setup_environment + init_getssl + create_certificate + assert_failure +} diff --git a/test/17-test-spaces-in-sans-dns01.bats b/test/17-test-spaces-in-sans-dns01.bats new file mode 100644 index 0000000..f1bf718 --- /dev/null +++ b/test/17-test-spaces-in-sans-dns01.bats @@ -0,0 +1,101 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" +} + + +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + + +setup_file() { + # Add hosts to DNS (also need to be added as aliases in docker-compose.yml) + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + for prefix in a b c; do + curl --silent -X POST -d '{"host":"'$prefix.$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/add-a + done + fi +} + + +teardown_file() { + if [ -z "$STAGING" ]; then + for prefix in a b c; do + curl --silent -X POST -d '{"host":"'$prefix.$GETSSL_HOST'"}' http://10.30.50.3:8055/clear-a + done + fi +} + + +@test "Test behaviour if SANS line is space separated instead of comma separated (dns01)" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + CONFIG_FILE="getssl-dns01-spaces-sans.cfg" + setup_environment + + init_getssl + create_certificate + assert_success + check_output_for_errors +} + + +@test "Test renewal if SANS line is space separated instead of comma separated (dns01)" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + run ${CODE_DIR}/getssl -U -d -f $GETSSL_HOST + assert_success + check_output_for_errors + cleanup_environment +} + + +@test "Test behaviour if SANS line is space separated and IGNORE_DIRECTORY_DOMAIN (dns01)" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + CONFIG_FILE="getssl-dns01-spaces-sans-and-ignore-dir-domain.cfg" + setup_environment + + init_getssl + create_certificate + assert_success + check_output_for_errors +} + + +@test "Test renewal if SANS line is space separated and IGNORE_DIRECTORY_DOMAIN (dns01)" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + run ${CODE_DIR}/getssl -U -d -f $GETSSL_HOST + assert_success + check_output_for_errors + cleanup_environment +} + + +@test "Test behaviour if SANS line is comma and space separated (dns01)" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + CONFIG_FILE="getssl-dns01-spaces-and-commas-sans.cfg" + setup_environment + + init_getssl + create_certificate + assert_success + check_output_for_errors + cleanup_environment +} diff --git a/test/17-test-spaces-in-sans-http01.bats b/test/17-test-spaces-in-sans-http01.bats new file mode 100644 index 0000000..b785546 --- /dev/null +++ b/test/17-test-spaces-in-sans-http01.bats @@ -0,0 +1,101 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" +} + + +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + + +setup_file() { + # Add hosts to DNS (also need to be added as aliases in docker-compose.yml) + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + for prefix in a b c; do + curl --silent -X POST -d '{"host":"'$prefix.$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/add-a + done + fi +} + + +teardown_file() { + if [ -z "$STAGING" ]; then + for prefix in a b c; do + curl --silent -X POST -d '{"host":"'$prefix.$GETSSL_HOST'"}' http://10.30.50.3:8055/clear-a + done + fi +} + + +@test "Test behaviour if SANS line is space separated instead of comma separated (http01)" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + CONFIG_FILE="getssl-http01-spaces-sans.cfg" + setup_environment + + init_getssl + create_certificate + assert_success + check_output_for_errors +} + + +@test "Test renewal if SANS line is space separated instead of comma separated (http01)" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + run ${CODE_DIR}/getssl -U -d -f $GETSSL_HOST + assert_success + check_output_for_errors + cleanup_environment +} + + +@test "Test behaviour if SANS line is space separated and IGNORE_DIRECTORY_DOMAIN (http01)" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + CONFIG_FILE="getssl-http01-spaces-sans-and-ignore-dir-domain.cfg" + setup_environment + + init_getssl + create_certificate + assert_success + check_output_for_errors +} + + +@test "Test renewal if SANS line is space separated and IGNORE_DIRECTORY_DOMAIN (http01)" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + run ${CODE_DIR}/getssl -U -d -f $GETSSL_HOST + assert_success + check_output_for_errors + cleanup_environment +} + + +@test "Test behaviour if SANS line is comma and space separated (http01)" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + CONFIG_FILE="getssl-http01-spaces-and-commas-sans.cfg" + setup_environment + + init_getssl + create_certificate + assert_success + check_output_for_errors + cleanup_environment +} diff --git a/test/18-retry-dns-add.bats b/test/18-retry-dns-add.bats new file mode 100644 index 0000000..11ca7f3 --- /dev/null +++ b/test/18-retry-dns-add.bats @@ -0,0 +1,42 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + +# This is run for every test +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt +} + + +@test "Check retry add dns command if dns isn't updated" { + if [ -n "$STAGING" ]; then + skip "Running internal tests, skipping external test" + fi + + CONFIG_FILE="getssl-dns01.cfg" + + setup_environment + init_getssl + + cat <<- EOF > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +DNS_ADD_COMMAND="/getssl/test/dns_add_fail" + +# Speed up the test by reducing the number or retries and the wait between retries. +DNS_WAIT=2 +DNS_WAIT_COUNT=11 +DNS_EXTRA_WAIT=0 +CHECK_ALL_AUTH_DNS="false" +CHECK_PUBLIC_DNS_SERVER="false" +DNS_WAIT_RETRY_ADD="true" +EOF + create_certificate + assert_failure + assert_line --partial "Retrying adding DNS via command" +} diff --git a/test/19-test-add-to-sans.bats b/test/19-test-add-to-sans.bats new file mode 100644 index 0000000..ea9650c --- /dev/null +++ b/test/19-test-add-to-sans.bats @@ -0,0 +1,177 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" +} + +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + + +setup_file() { + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + curl --silent -X POST -d '{"host":"a.'$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/add-a + fi +} + + +teardown_file() { + if [ -z "$STAGING" ]; then + curl --silent -X POST -d '{"host":"a.'$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/clear-a + fi +} + + +@test "Create certificate to check can add to SANS" { + if [ -n "$STAGING" ]; then + skip "Not trying on staging server yet" + fi + CONFIG_FILE="getssl-dns01.cfg" + setup_environment + init_getssl + + create_certificate + assert_success + check_output_for_errors +} + + +@test "Check that if the SANS doesn't change, we don't re-create the certificate (single domain)" { + if [ -n "$STAGING" ]; then + skip "Not trying on staging server yet" + fi + CONFIG_FILE="getssl-dns01.cfg" + + . "${CODE_DIR}/test/test-config/${CONFIG_FILE}" + CERT=${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt + + create_certificate + assert_success + check_output_for_errors + + # As the SANS list didn't change, a new certificate isn't needed + refute_line --partial "does not match domains requested" + refute_line --partial "does not have the same domains as the config - re-create-csr" + refute_line --partial "certificate installed OK on server" + assert_line --partial 'certificate is valid for more than' + + # Check that the SAN list in the certificate matches the expected value + SAN_IN_CERT=$(openssl x509 -in "$CERT" -noout -text | grep "DNS:" | sed 's/^ *//g') + SAN_EXPECTED="DNS:${GETSSL_HOST}" + if [[ "$SAN_IN_CERT" != "$SAN_EXPECTED" ]]; then + echo "# SAN_IN_CERT=$SAN_IN_CERT" + echo "# SAN_EXPECTED=$SAN_EXPECTED" + fi + [ "${SAN_IN_CERT}" = "$SAN_EXPECTED" ] +} + + +@test "Check certificate is recreated if we add a new domain to SANS" { + if [ -n "$STAGING" ]; then + skip "Not trying on staging server yet" + fi + CONFIG_FILE="getssl-dns01.cfg" + + cat <<- EOF > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +SANS="a.${GETSSL_HOST}" +EOF + + . "${CODE_DIR}/test/test-config/${CONFIG_FILE}" + CERT=${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt + + create_certificate + assert_success + check_output_for_errors + + # As the SANS list changed, a new certificate is needed + assert_line --partial "does not match domains requested" + assert_line --partial "does not have the same domains as the config - re-create-csr" + assert_line --partial "certificate installed OK on server" + refute_line --partial 'certificate is valid for more than' + + # Check that the SAN list in the certificate matches the expected value + SAN_IN_CERT=$(openssl x509 -in "$CERT" -noout -text | grep "DNS:" | sed 's/^ *//g') + SAN_EXPECTED="DNS:${GETSSL_HOST}, DNS:a.${GETSSL_HOST}" + if [[ "$SAN_IN_CERT" != "$SAN_EXPECTED" ]]; then + echo "# SAN_IN_CERT=$SAN_IN_CERT" + echo "# SAN_EXPECTED=$SAN_EXPECTED" + fi + [ "${SAN_IN_CERT}" = "$SAN_EXPECTED" ] +} + + +@test "Check that if the SANS doesn't change, we don't re-create the certificate (multiple domains)" { + if [ -n "$STAGING" ]; then + skip "Not trying on staging server yet" + fi + CONFIG_FILE="getssl-dns01.cfg" + + cat <<- EOF > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +SANS="a.${GETSSL_HOST}" +EOF + + . "${CODE_DIR}/test/test-config/${CONFIG_FILE}" + CERT=${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt + + create_certificate + assert_success + check_output_for_errors + + # As the SANS list didn't change, a new certificate isn't needed + refute_line --partial "does not match domains requested" + refute_line --partial "does not have the same domains as the config - re-create-csr" + refute_line --partial "certificate installed OK on server" + assert_line --partial 'certificate is valid for more than' + + # Check that the SAN list in the certificate matches the expected value + SAN_IN_CERT=$(openssl x509 -in "$CERT" -noout -text | grep "DNS:" | sed 's/^ *//g') + SAN_EXPECTED="DNS:${GETSSL_HOST}, DNS:a.${GETSSL_HOST}" + if [[ "$SAN_IN_CERT" != "$SAN_EXPECTED" ]]; then + echo "# SAN_IN_CERT=$SAN_IN_CERT" + echo "# SAN_EXPECTED=$SAN_EXPECTED" + fi + [ "${SAN_IN_CERT}" = "$SAN_EXPECTED" ] +} + + +@test "Check that if the SANS doesn't change, we don't re-create the certificate (reordered domains)" { + if [ -n "$STAGING" ]; then + skip "Not trying on staging server yet" + fi + CONFIG_FILE="getssl-dns01.cfg" + + cat <<- EOF > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +IGNORE_DIRECTORY_DOMAIN="true" +SANS="a.${GETSSL_HOST}, ${GETSSL_HOST}" +EOF + + . "${CODE_DIR}/test/test-config/${CONFIG_FILE}" + CERT=${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt + + create_certificate + assert_success + check_output_for_errors + + # As the SANS list didn't change, a new certificate isn't needed + refute_line --partial "does not match domains requested" + refute_line --partial "does not have the same domains as the config - re-create-csr" + refute_line --partial "certificate installed OK on server" + assert_line --partial 'certificate is valid for more than' + + # Check that the SAN list in the certificate matches the expected value + SAN_IN_CERT=$(openssl x509 -in "$CERT" -noout -text | grep "DNS:" | sed 's/^ *//g') + SAN_EXPECTED="DNS:${GETSSL_HOST}, DNS:a.${GETSSL_HOST}" + if [[ "$SAN_IN_CERT" != "$SAN_EXPECTED" ]]; then + echo "# SAN_IN_CERT=$SAN_IN_CERT" + echo "# SAN_EXPECTED=$SAN_EXPECTED" + fi + [ "${SAN_IN_CERT}" = "$SAN_EXPECTED" ] +} diff --git a/test/2-simple-dns01-dig.bats b/test/2-simple-dns01-dig.bats index cbac598..c5bc854 100644 --- a/test/2-simple-dns01-dig.bats +++ b/test/2-simple-dns01-dig.bats @@ -5,38 +5,55 @@ load '/bats-assert/load.bash' load '/getssl/test/test_helper.bash' -# This is run for every test -setup() { - export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt +setup_file() { + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + fi + if [ -f /usr/bin/host ]; then + mv /usr/bin/host /usr/bin/host.getssl.bak + fi + if [ -f /usr/bin/nslookup ]; then + mv /usr/bin/nslookup /usr/bin/nslookup.getssl.bak + fi } -@test "Create new certificate using DNS-01 verification (dig)" { - if [ -n "$STAGING" ]; then - skip "Using staging server, skipping internal test" +teardown_file() { + if [ -f /usr/bin/host.getssl.bak ]; then + mv /usr/bin/host.getssl.bak /usr/bin/host fi + if [ -f /usr/bin/nslookup.getssl.bak ]; then + mv /usr/bin/nslookup.getssl.bak /usr/bin/nslookup + fi +} + + +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" +} + + +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + +@test "Create new certificate using DNS-01 verification (dig)" { CONFIG_FILE="getssl-dns01.cfg" + setup_environment init_getssl - create_certificate -d + create_certificate assert_success assert_output --partial "dig" - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' # don't fail for :error:badNonce - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + check_output_for_errors } @test "Force renewal of certificate using DNS-01 (dig)" { - if [ -n "$STAGING" ]; then - skip "Using staging server, skipping internal test" - fi - run ${CODE_DIR}/getssl -d -f $GETSSL_HOST + run ${CODE_DIR}/getssl -U -d -f $GETSSL_HOST assert_success assert_output --partial "dig" - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' # don't fail for :error:badNonce - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + check_output_for_errors cleanup_environment } diff --git a/test/2-simple-dns01-nslookup.bats b/test/2-simple-dns01-nslookup.bats index f92d817..37fa121 100644 --- a/test/2-simple-dns01-nslookup.bats +++ b/test/2-simple-dns01-nslookup.bats @@ -7,28 +7,37 @@ load '/getssl/test/test_helper.bash' # This is run for every test setup() { - export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt - mv /usr/bin/dig /usr/bin/dig.getssl.bak + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + fi + if [ -f /usr/bin/dig ]; then + mv /usr/bin/dig /usr/bin/dig.getssl.bak + fi + if [ -f /usr/bin/host ]; then + mv /usr/bin/host /usr/bin/host.getssl.bak + fi } teardown() { - mv /usr/bin/dig.getssl.bak /usr/bin/dig + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip + if [ -f /usr/bin/dig.getssl.bak ]; then + mv /usr/bin/dig.getssl.bak /usr/bin/dig + fi + if [ -f /usr/bin/host.getssl.bak ]; then + mv /usr/bin/host.getssl.bak /usr/bin/host + fi } @test "Create new certificate using DNS-01 verification (nslookup)" { - if [ -n "$STAGING" ]; then - skip "Using staging server, skipping internal test" - fi - CONFIG_FILE="getssl-dns01.cfg" + setup_environment init_getssl - create_certificate -d + create_certificate assert_success assert_output --partial "nslookup" - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' # don't fail for :error:badNonce - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + check_output_for_errors "debug" } diff --git a/test/20-wildcard-simple.bats b/test/20-wildcard-simple.bats new file mode 100644 index 0000000..c99fda5 --- /dev/null +++ b/test/20-wildcard-simple.bats @@ -0,0 +1,69 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + fi +} + + +@test "Create wildcard certificate" { + CONFIG_FILE="getssl-dns01.cfg" + + GETSSL_CMD_HOST="*.${GETSSL_HOST}" + setup_environment + init_getssl + create_certificate + assert_success + check_output_for_errors +} + + +@test "Check CHECK_REMOTE works for wildcard certificates" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + + run ${CODE_DIR}/getssl -U -d "*.$GETSSL_HOST" + assert_success + assert_line --partial "certificate is valid for more than" + check_output_for_errors +} + + +@test "Force renewal of wildcard certificate" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + + run ${CODE_DIR}/getssl -U -d -f "*.$GETSSL_HOST" + assert_success + refute_line --partial "certificate is valid for more than" + check_output_for_errors +} + + +@test "Check renewal of near-expiration wildcard certificate" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + + echo "RENEW_ALLOW=2000" >> "${INSTALL_DIR}/.getssl/*.${GETSSL_HOST}/getssl.cfg" + + run ${CODE_DIR}/getssl -U -d "*.$GETSSL_HOST" + assert_success + refute_line --partial "certificate is valid for more than" + check_output_for_errors + cleanup_environment +} diff --git a/test/21-wildcard-dual-rsa.bats b/test/21-wildcard-dual-rsa.bats new file mode 100644 index 0000000..395de22 --- /dev/null +++ b/test/21-wildcard-dual-rsa.bats @@ -0,0 +1,79 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + fi +} + + +@test "Create secp384r1 wildcard certificate" { + CONFIG_FILE="getssl-dns01.cfg" + + GETSSL_CMD_HOST="*.${GETSSL_HOST}" + + setup_environment + init_getssl + + cat <<- EOF > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +ACCOUNT_KEY_TYPE="secp384r1" +PRIVATE_KEY_ALG="secp384r1" +EOF + + create_certificate + assert_success + check_output_for_errors + run openssl x509 -noout -text -in "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt" + assert_line --partial "Public Key Algorithm: id-ecPublicKey" + cleanup_environment +} + + +@test "Create dual certificates using DNS-01 verification" { + CONFIG_FILE="getssl-dns01.cfg" + + GETSSL_CMD_HOST="*.${GETSSL_HOST}" + + setup_environment + init_getssl + + cat <<- EOF > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +DUAL_RSA_ECDSA="true" +ACCOUNT_KEY_TYPE="prime256v1" +PRIVATE_KEY_ALG="prime256v1" +EOF + + check_nginx + if [ "$OLD_NGINX" = "false" ]; then + echo 'RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-dual-certs ${NGINX_CONFIG} && /getssl/test/restart-nginx"' >> ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg + else + echo 'CHECK_REMOTE="false"' >> ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg + fi + + create_certificate + assert_success + check_output_for_errors + check_certificates + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/chain.ec.crt" ] + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.ec.crt" ] + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.ec.crt" ] + + run openssl x509 -noout -text -in "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt" + assert_line --partial "Public Key Algorithm: rsaEncryption" + + run openssl x509 -noout -text -in "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.ec.crt" + assert_line --partial "Public Key Algorithm: id-ecPublicKey" + + cleanup_environment +} diff --git a/test/22-wildcard-dual-rsa-ecdsa-copy-2-locations.bats b/test/22-wildcard-dual-rsa-ecdsa-copy-2-locations.bats new file mode 100644 index 0000000..51d966b --- /dev/null +++ b/test/22-wildcard-dual-rsa-ecdsa-copy-2-locations.bats @@ -0,0 +1,66 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# These are run for every test, not once per file +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + fi +} + + +@test "Create dual certificates (one wildcard) and copy RSA and ECDSA chain and key to two locations" { + CONFIG_FILE="getssl-dns01.cfg" + + GETSSL_CMD_HOST="*.${GETSSL_HOST}" + + setup_environment + init_getssl + + cat <<- 'EOF' > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +DUAL_RSA_ECDSA="true" +ACCOUNT_KEY_TYPE="prime256v1" +PRIVATE_KEY_ALG="prime256v1" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key;/root/a.${GETSSL_HOST}/server.key" +DOMAIN_CHAIN_LOCATION="/etc/nginx/pki/domain-chain.crt;/root/a.${GETSSL_HOST}/domain-chain.crt" # this is the domain cert and CA cert +EOF + + check_nginx + if [ "$OLD_NGINX" = "false" ]; then + echo 'RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-dual-certs ${NGINX_CONFIG} && /getssl/test/restart-nginx"' >> ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg + else + echo 'CHECK_REMOTE="false"' >> ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg + fi + + create_certificate + assert_success + check_output_for_errors + + if [ "$OLD_NGINX" = "false" ]; then + assert_line --partial "rsa certificate installed OK on server" + assert_line --partial "prime256v1 certificate installed OK on server" + fi + + # Check that the RSA chain and key have been copied to both locations + assert [ -e "/etc/nginx/pki/domain-chain.crt" ] + assert [ -e "/root/a.${GETSSL_HOST}/domain-chain.crt" ] + assert [ -e "/etc/nginx/pki/private/server.key" ] + assert [ -e "/root/a.${GETSSL_HOST}/server.key" ] + + # Check that the ECDSA chain and key have been copied to both locations + assert [ -e "/etc/nginx/pki/domain-chain.ec.crt" ] + assert [ -e "/root/a.${GETSSL_HOST}/domain-chain.ec.crt" ] + assert [ -e "/etc/nginx/pki/private/server.ec.key" ] + assert [ -e "/root/a.${GETSSL_HOST}/server.ec.key" ] + + cleanup_environment +} diff --git a/test/23-wildcard-check-globbing.bats b/test/23-wildcard-check-globbing.bats new file mode 100644 index 0000000..636bd33 --- /dev/null +++ b/test/23-wildcard-check-globbing.bats @@ -0,0 +1,51 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + fi +} + + +@test "Check for globbing for wildcard domains" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + else + CONFIG_FILE="getssl-dns01.cfg" + fi + + GETSSL_CMD_HOST="*.${GETSSL_HOST}" + setup_environment + + init_getssl + + # Create a directory in /root which looks like a domain so that if glob expansion is performed a certificate for the wrong domain will be created + mkdir -p "${INSTALL_DIR}/a.${GETSSL_HOST}" + + create_certificate + assert_success + check_output_for_errors +} + + +@test "Force renewal of wildcard certificate" { + if [ -n "$STAGING" ]; then + skip "Not trying on staging server yet" + fi + + run ${CODE_DIR}/getssl -U -d -f "*.$GETSSL_HOST" + assert_success + refute_line --partial "certificate is valid for more than" + check_output_for_errors +} diff --git a/test/24-wildcard-sans.bats b/test/24-wildcard-sans.bats new file mode 100644 index 0000000..1ce09c2 --- /dev/null +++ b/test/24-wildcard-sans.bats @@ -0,0 +1,74 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" +} + + +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + + +setup_file() { + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + curl --silent -X POST -d '{"host":"wild-'$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/add-a + fi +} + + +teardown_file() { + if [ -z "$STAGING" ]; then + curl --silent -X POST -d '{"host":"wild-'$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/clear-a + fi +} + + +@test "Check can create certificate for wildcard domain as arg and non-wildcard in SANS" { + CONFIG_FILE="getssl-dns01.cfg" + + # Staging server generates an error if try to create a certificate for *.domain and a.domain + # so create for *.wild-domain and a.domain instead + GETSSL_CMD_HOST="*.wild-${GETSSL_HOST}" + setup_environment + init_getssl + + echo 'SANS="${GETSSL_HOST}"' > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg + if [ -n "$STAGING" ]; then + echo 'CHECK_REMOTE="false"' >> ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg + fi + + create_certificate + assert_success + check_output_for_errors + run openssl x509 -noout -text -in "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt" + # verify certificate is for wildcard domain with non-wildcard domain in the Subject Alternative Name list + assert_output --regexp "Subject: CN[ ]?=[ ]?\*.wild-${GETSSL_HOST}" + assert_output --partial "DNS:${GETSSL_HOST}" +} + + +@test "Check can create certificate for non-wildcard domain as arg and wildcard in SANS" { + CONFIG_FILE="getssl-dns01.cfg" + + GETSSL_CMD_HOST="${GETSSL_HOST}" + setup_environment + init_getssl + + echo 'SANS="*.wild-${GETSSL_HOST}"' > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg + + create_certificate + assert_success + check_output_for_errors + run openssl x509 -noout -text -in "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt" + # verify certificate is for non-wildcard domain with wildcard domain in the Subject Alternative Name list + assert_output --regexp "Subject: CN[ ]?=[ ]?${GETSSL_HOST}" + assert_output --partial "DNS:*.wild-${GETSSL_HOST}" +} diff --git a/test/25-wildcard-all.bats b/test/25-wildcard-all.bats new file mode 100644 index 0000000..64b6c6d --- /dev/null +++ b/test/25-wildcard-all.bats @@ -0,0 +1,47 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + fi +} + + +@test "Check can create certificate for wildcard domain using --all" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + else + CONFIG_FILE="getssl-dns01.cfg" + fi + + GETSSL_CMD_HOST="*.${GETSSL_HOST}" + setup_environment + # Create .getssl directory and .getssl/*.{host} directory + init_getssl + cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/*.${GETSSL_HOST}/getssl.cfg" + + # create another domain in the .getssl directory + run ${CODE_DIR}/getssl -U -d -c "a.${GETSSL_HOST}" + cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/a.${GETSSL_HOST}/getssl.cfg" + + # Create a directory in /root which looks like a domain so that if glob expansion is performed the wildcard certificate won't be created + mkdir -p "${INSTALL_DIR}/a.${GETSSL_HOST}" + + run ${CODE_DIR}/getssl -U -d --all + + assert_success + assert_line --partial "Certificate saved in /root/.getssl/*.${GETSSL_HOST}/*.${GETSSL_HOST}" + assert_line --partial "Certificate saved in /root/.getssl/a.${GETSSL_HOST}/a.${GETSSL_HOST}" + check_output_for_errors +} diff --git a/test/26-wildcard-revoke.bats b/test/26-wildcard-revoke.bats new file mode 100644 index 0000000..5b61e00 --- /dev/null +++ b/test/26-wildcard-revoke.bats @@ -0,0 +1,46 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + fi +} + + +@test "Create certificate to check wildcard revoke" { + CONFIG_FILE="getssl-dns01.cfg" + + GETSSL_CMD_HOST="*.${GETSSL_HOST}" + setup_environment + init_getssl + create_certificate + assert_success + check_output_for_errors +} + + +@test "Check we can revoke a wildcard certificate" { + CONFIG_FILE="getssl-dns01.cfg" + . "${CODE_DIR}/test/test-config/${CONFIG_FILE}" + + GETSSL_CMD_HOST="*.${GETSSL_HOST}" + + CERT=${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt + KEY=${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.key + + run ${CODE_DIR}/getssl -U -d --revoke $CERT $KEY $CA + assert_line --partial "certificate revoked" + assert_success + check_output_for_errors +} diff --git a/test/27-wildcard-existing-cert.bats b/test/27-wildcard-existing-cert.bats new file mode 100644 index 0000000..dcb74e6 --- /dev/null +++ b/test/27-wildcard-existing-cert.bats @@ -0,0 +1,50 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + fi +} + + +@test "Check that new creating a new configuration files uses details from existing certificate" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + else + CONFIG_FILE="getssl-dns01.cfg" + fi + + # Create and install certificate for wildcard + another domain + GETSSL_CMD_HOST="*.${GETSSL_HOST}" + setup_environment + init_getssl + + echo 'SANS="a.${GETSSL_HOST}"' > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg + + create_certificate + assert_success + check_output_for_errors + + # Delete configuration + rm -r ${INSTALL_DIR}/.getssl + + # Create configuration + run ${CODE_DIR}/getssl -U -d -c "${GETSSL_CMD_HOST}" + + # Assert that the newly created configuration contains the additional domain in SANS + # if this fails then error in tests will be "grep failed" - this means SANS did not hold the expected value + # eg SANS="a.centos7.getssl.test" + grep -q "SANS=\"a.${GETSSL_HOST}\"" ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl.cfg + assert_success +} diff --git a/test/28-wildcard-error-http01-validation.bats b/test/28-wildcard-error-http01-validation.bats new file mode 100644 index 0000000..e90db22 --- /dev/null +++ b/test/28-wildcard-error-http01-validation.bats @@ -0,0 +1,36 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + fi +} + + +@test "Check that trying to create a wildcard certificate using http-01 validation shows an error message" { + if [ -n "$STAGING" ]; then + skip "Internal test, no need to test on staging server" + else + CONFIG_FILE="getssl-http01.cfg" + fi + + # Try and create a wildcard certificate using http-01 validation + GETSSL_CMD_HOST="*.${GETSSL_HOST}" + setup_environment + init_getssl + + create_certificate + assert_failure + assert_line --partial "cannot use http-01 validation for wildcard domains" +} diff --git a/test/29-check-mktemp-failure.bats b/test/29-check-mktemp-failure.bats new file mode 100644 index 0000000..566c7a5 --- /dev/null +++ b/test/29-check-mktemp-failure.bats @@ -0,0 +1,52 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + fi +} + + +@test "Check that getssl -c fails with an error message if mktemp fails" { + if [ -n "$STAGING" ]; then + skip "Internal test, no need to test on staging server" + else + CONFIG_FILE="getssl-http01.cfg" + fi + + # set TMPDIR to an invalid directory and check for failure + export TMPDIR=/getssl.invalid.directory + setup_environment + run ${CODE_DIR}/getssl -U -d -c "$GETSSL_CMD_HOST" + assert_failure + assert_line --partial "mktemp failed" +} + + +@test "Check that getssl fails with an error message if mktemp fails" { + if [ -n "$STAGING" ]; then + skip "Internal test, no need to test on staging server" + else + CONFIG_FILE="getssl-http01.cfg" + fi + + setup_environment + init_getssl + + # set TMPDIR to an invalid directory and check for failure + export TMPDIR=/getssl.invalid.directory + create_certificate + assert_failure + assert_line --partial "mktemp failed" +} diff --git a/test/3-dual-rsa-ecdsa.bats b/test/3-dual-rsa-ecdsa.bats index 7820a96..d230ab4 100644 --- a/test/3-dual-rsa-ecdsa.bats +++ b/test/3-dual-rsa-ecdsa.bats @@ -6,7 +6,12 @@ load '/getssl/test/test_helper.bash' # This is run for every test +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt } @@ -15,11 +20,39 @@ setup() { if [ -n "$STAGING" ]; then skip "Using staging server, skipping internal test" fi - CONFIG_FILE="getssl-http01-dual-rsa-ecdsa.cfg" + + check_nginx + if [ "$OLD_NGINX" = "false" ]; then + CONFIG_FILE="getssl-http01-dual-rsa-ecdsa.cfg" + else + CONFIG_FILE="getssl-http01-dual-rsa-ecdsa-old-nginx.cfg" + fi + setup_environment init_getssl create_certificate assert_success + check_output_for_errors + check_certificates + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/chain.ec.crt" ] + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.ec.crt" ] + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.ec.crt" ] +} + + +@test "Check renewal test works for dual certificates using HTTP-01" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + check_nginx + run ${CODE_DIR}/getssl -U -d $GETSSL_HOST + + if [ "$OLD_NGINX" = "false" ]; then + assert_line --partial "certificate on server is same as the local cert" + else + assert_line --partial "certificate is valid for more than 30 days" + fi + assert_success } @@ -27,19 +60,33 @@ setup() { if [ -n "$STAGING" ]; then skip "Using staging server, skipping internal test" fi - run ${CODE_DIR}/getssl -f $GETSSL_HOST + run ${CODE_DIR}/getssl -U -f $GETSSL_HOST assert_success + check_output_for_errors } + @test "Create dual certificates using DNS-01 verification" { if [ -n "$STAGING" ]; then skip "Using staging server, skipping internal test" fi - CONFIG_FILE="getssl-dns01-dual-rsa-ecdsa.cfg" + + check_nginx + if [ "$OLD_NGINX" = "false" ]; then + CONFIG_FILE="getssl-dns01-dual-rsa-ecdsa.cfg" + else + CONFIG_FILE="getssl-dns01-dual-rsa-ecdsa-old-nginx.cfg" + fi + setup_environment init_getssl create_certificate assert_success + check_output_for_errors + check_certificates + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/chain.ec.crt" ] + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.ec.crt" ] + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.ec.crt" ] } @@ -47,7 +94,8 @@ setup() { if [ -n "$STAGING" ]; then skip "Using staging server, skipping internal test" fi - run ${CODE_DIR}/getssl -f $GETSSL_HOST + run ${CODE_DIR}/getssl -U -f $GETSSL_HOST assert_success + check_output_for_errors cleanup_environment } diff --git a/test/30-handle-dig-failure.bats b/test/30-handle-dig-failure.bats new file mode 100644 index 0000000..5b7edb1 --- /dev/null +++ b/test/30-handle-dig-failure.bats @@ -0,0 +1,46 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + if [ -f /usr/bin/drill ]; then + mv /usr/bin/drill /usr/bin/drill.getssl.bak + fi + if [ -f /usr/bin/dig ]; then + chmod -x /usr/bin/dig + fi +} + + +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip + if [ -f /usr/bin/drill.getssl.bak ]; then + mv /usr/bin/drill.getssl.bak /usr/bin/drill + fi + if [ -f /usr/bin/dig ]; then + chmod +x /usr/bin/dig + fi +} + + +@test "Test that if dig exists but errors HAS_DIG is not set" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + if [ ! -f /usr/bin/dig ]; then + skip "dig not installed, skipping dig test" + fi + CONFIG_FILE="getssl-http01.cfg" + setup_environment + init_getssl + create_certificate + assert_success + refute_line --partial "HAS DIG_OR_DRILL=dig" + check_output_for_errors +} diff --git a/test/31-test-posix-error.bats b/test/31-test-posix-error.bats new file mode 100644 index 0000000..fece7ff --- /dev/null +++ b/test/31-test-posix-error.bats @@ -0,0 +1,30 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt +} + + +@test "Test that running in POSIX mode shows an error" { + # v2.31 uses read to create an array in the get_auth_dns function which causes a parse error in posix mode + # Could be re-written to not use this functionality if it causes for required. + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + + run bash --posix "${CODE_DIR}/getssl" -U -d + assert_failure + assert_line --partial "getssl: Running with POSIX mode enabled is not supported" + check_output_for_errors +} diff --git a/test/32-test-upgrade.bats b/test/32-test-upgrade.bats new file mode 100644 index 0000000..e698fc5 --- /dev/null +++ b/test/32-test-upgrade.bats @@ -0,0 +1,192 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + +LIMIT_API="https://api.github.com/rate_limit" + +# Quota generally shouldn't be an issue - except for tests +# Rate limits are per-IP address +check_github_quota() { + local need remaining reset limits now + need="$1" + while true ; do + limits="$(curl ${_NOMETER:---silent} --user-agent "$CURL_USERAGENT" -H 'Accept: application/vnd.github.v3+json' "$LIMIT_API" | sed -e's/\("[^:]*": *\("[^""]*",\|[^,]*[,}]\)\)/\r\n\1/g' | sed -ne'/"core":/,/}/p')" + errcode=$? + if [[ $errcode -eq 60 ]]; then + error_exit "curl needs updating, your version does not support SNI (multiple SSL domains on a single IP)" + elif [[ $errcode -gt 0 ]]; then + error_exit "curl error checking releases: $errcode" + fi + limits="$(sed -e's/^ *//g' <<<"${limits}")" + remaining="$(sed -e'/^"remaining": *[0-9]/!d;s/^"remaining": *\([0-9][0-9]*\).*$/\1/' <<<"${limits}")" + reset="$(sed -e'/^"reset": *[0-9]/!d;s/^"reset": *\([0-9][0-9]*\).*$/\1/' <<<"${limits}")" + if [[ "$remaining" -ge "$need" ]] ; then return 0 ; fi + limit="$(sed -e'/^"limit": *[0-9]/!d;s/^"limit": *\([0-9][0-9]*\).*$/\1/' <<<"${limits}")" + if [[ "$limit" -lt "$need" ]] ; then + error_exit "GitHub API request $need exceeds limit $limit" + fi + now="$(date +%s)" + while [[ "$now" -lt "$reset" ]] ; do + info "sleeping $(( "$reset" - "$now" )) seconds for GitHub quota" + sleep "$(( "$reset" - "$now" ))" + now="$(date +%s)" + done + done +} + + +setup_file() { + if [ -f $BATS_RUN_TMPDIR/failed.skip ]; then + echo "# Skipping setup due to previous test failure" >&3 + return 0 + fi + local n + # Not every tag reflects a stable release. Ask GitHub for the releases & identify the last two. + # This is sorted by creation date of the release tag, not the publication date. This matches + # GitHub's releases/latest, which is how getssl determines what's available. + # This is expensive, so do it only once + + . "${CODE_DIR}/getssl" -U --source + check_github_quota 7 + export RELEASES="$(mktemp 2>/dev/null || mktemp -t getssl.XXXXXX)" + if [ -z "$RELEASES" ]; then + echo "# mktemp failed" >&3 + return 1 + fi + if ! curl ${_NOMETER:---silent} --user-agent "$CURL_USERAGENT" \ + -H 'Accept: application/vnd.github.v3+json' "${RELEASE_API%/latest}" | \ + jq 'map(select((.draft or .prerelease)|not))|sort_by(.created_at)|reverse' >"$RELEASES" ; then + errcode="$?" + echo "# Failed to download release information from ${RELEASE_API%/latest} $errcode" >&3 + return "$errcode" + fi + n="$(jq '.|length' <$RELEASES)" + if [[ "$n" < 2 ]]; then + echo "# Fewer than 2 ($n) stable releases detected in ${RELEASE_API%/latest}, can not run upgrade tests" >&3 + return 0 + fi + CURRENT_TAG="$(jq -r '.[0].tag_name' <"$RELEASES")" + export CURRENT_TAG="${CURRENT_TAG:1}" + PREVIOUS_TAG="$(jq -r '.[1].tag_name' <"$RELEASES")" + export PREVIOUS_TAG="${PREVIOUS_TAG:1}" +} + +teardown_file() { + [ -n "$RELEASES" ] && rm -f "$RELEASES" + true +} + +# This is run for every test +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + [ -z "$PREVIOUS_TAG" ] && skip "Skipping upgrade test because no previous release detected" + + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + + # Turn off warning about detached head + git config --global advice.detachedHead false + if [[ -n "${GITHUB_REPOSITORY}" ]] ; then + _REPO="https://github.com/${GITHUB_REPOSITORY}.git" + else + _REPO="https://github.com/srvrco/getssl.git" + fi + run git clone "${_REPO}" "$INSTALL_DIR/upgrade-getssl" + + + cd "$INSTALL_DIR/upgrade-getssl" + + # The version in the file, which we will overwrite + FILE_VERSION=$(awk -F'"' '/^VERSION=/{print $2}' "$CODE_DIR/getssl") + # If FILE_VERSION > CURRENT_TAG then either we are testing a push to master or the last version wasn't released +} + + +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip + [ -d "$INSTALL_DIR/upgrade-getssl" ] && rm -r "$INSTALL_DIR/upgrade-getssl" + true +} + + +@test "Test that we are told that a newer version is available" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + + cd "$INSTALL_DIR/upgrade-getssl" + git checkout tags/v${PREVIOUS_TAG} + + CONFIG_FILE="getssl-http01.cfg" + setup_environment + init_getssl + cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl.cfg" + + # Overwrite checked out getssl-script with copy of new one, but write the previous version into the copy + # Note that this way we mock downgrading getssl and are testing the upgrading of the version in development + cp "$CODE_DIR/getssl" "$INSTALL_DIR/upgrade-getssl/" + sed -i -e "s/VERSION=\"${FILE_VERSION}\"/VERSION=\"${PREVIOUS_TAG}\"/" "$INSTALL_DIR/upgrade-getssl/getssl" + + run "$INSTALL_DIR/upgrade-getssl/getssl" -d --check-config ${GETSSL_CMD_HOST} + assert_success + + # Check for current tag or file version otherwise push to master fails on a new version (or if the tag hasn't been updated) + assert_line --regexp "A more recent version \(v(${CURRENT_TAG}|${FILE_VERSION})\) than .* of getssl is available, please update" + # output can contain "error" in release description + check_output_for_errors +} + + +@test "Test that we can upgrade to the newer version" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + + cd "$INSTALL_DIR/upgrade-getssl" + git checkout tags/v${CURRENT_TAG} + + CONFIG_FILE="getssl-http01.cfg" + setup_environment + init_getssl + cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl.cfg" + + # Overwrite checked out getssl-script with copy of new one, but write the previous version into the copy + # Note that this way we mock downgrading getssl and are testing the upgrading of the version in development + cp "$CODE_DIR/getssl" "$INSTALL_DIR/upgrade-getssl/" + sed -i -e "s/VERSION=\"${FILE_VERSION}\"/VERSION=\"${PREVIOUS_TAG}\"/" "$INSTALL_DIR/upgrade-getssl/getssl" + + run "$INSTALL_DIR/upgrade-getssl/getssl" -d --check-config --upgrade ${GETSSL_CMD_HOST} + assert_success + + # Check for current tag or file version otherwise push to master fails on a new version (or if the tag hasn't been updated) + assert_line --regexp "Installed v(${CURRENT_TAG}|${FILE_VERSION}), restarting" + assert_line --partial "Configuration check successful" +} + + +@test "Test that we can upgrade to the newer version when invoking as \"bash ./getssl\"" { + # Note that `bash getssl` will fail if the CWD isn't in the PATH and an upgrade occurs + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + + cd "$INSTALL_DIR/upgrade-getssl" + git checkout tags/v${PREVIOUS_TAG} + + CONFIG_FILE="getssl-http01.cfg" + setup_environment + init_getssl + cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl.cfg" + + # Overwrite checked out getssl-script with copy of new one, but write the previous version into the copy + # Note that this way we mock downgrading getssl and are testing the upgrading of the version in development + cp "$CODE_DIR/getssl" "$INSTALL_DIR/upgrade-getssl/" + sed -i -e "s/VERSION=\"${FILE_VERSION}\"/VERSION=\"${PREVIOUS_TAG}\"/" "$INSTALL_DIR/upgrade-getssl/getssl" + + run bash ./getssl -d --check-config --upgrade ${GETSSL_CMD_HOST} + assert_success + + # Check for current tag or file version otherwise push to master fails on a new version (or if the tag hasn't been updated) + assert_line --regexp "Installed v(${CURRENT_TAG}|${FILE_VERSION}), restarting" +} diff --git a/test/33-ftp.bats b/test/33-ftp.bats new file mode 100644 index 0000000..59297c0 --- /dev/null +++ b/test/33-ftp.bats @@ -0,0 +1,73 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + if [ -n "${VSFTPD_CONF}" ]; then + cp $VSFTPD_CONF ${VSFTPD_CONF}.getssl + + # enable passive and disable active mode + # https://www.pixelstech.net/article/1364817664-FTP-active-mode-and-passive-mode + cat <<- _FTP >> $VSFTPD_CONF +pasv_enable=NO +_FTP + + ${CODE_DIR}/test/restart-ftpd start + fi +} + + +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip + if [ -n "${VSFTPD_CONF}" ]; then + cp ${VSFTPD_CONF}.getssl $VSFTPD_CONF + ${CODE_DIR}/test/restart-ftpd stop + fi +} + + +@test "Use FTP to create challenge file" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + + if [[ ! -d /var/www/html/.well-known/acme-challenge ]]; then + mkdir -p /var/www/html/.well-known/acme-challenge + fi + + # Always change ownership and permissions in case previous tests created the directories as root + chgrp -R www-data /var/www/html/.well-known + chmod -R g+w /var/www/html/.well-known + + CONFIG_FILE="getssl-http01.cfg" + setup_environment + init_getssl + + cat <<- EOF > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +ACL="ftp:ftpuser:ftpuser:${GETSSL_CMD_HOST}:/var/www/html/.well-known/acme-challenge" +EOF + + if [[ "$GETSSL_OS" = "alpine" ]]; then + cat <<- EOF2 >> ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +FTP_OPTIONS="set ftp:passive-mode off" +EOF2 + elif [[ "$FTP_PASSIVE_DEFAULT" == "true" ]]; then + cat <<- EOF3 >> ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +FTP_OPTIONS="passive" +EOF3 + fi + + create_certificate + assert_success + assert_line --partial "ftp:ftpuser:ftpuser:" + if [[ "$GETSSL_OS" != "alpine" ]] && [[ "$FTP_PASSIVE_DEFAULT" == "true" ]]; then + assert_line --partial "Passive mode off" + fi + check_output_for_errors +} diff --git a/test/34-ftp-passive.bats b/test/34-ftp-passive.bats new file mode 100644 index 0000000..71e6ed0 --- /dev/null +++ b/test/34-ftp-passive.bats @@ -0,0 +1,73 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + if [ -n "${VSFTPD_CONF}" ]; then + cp $VSFTPD_CONF ${VSFTPD_CONF}.getssl + + # enable passive and disable active mode + # https://www.pixelstech.net/article/1364817664-FTP-active-mode-and-passive-mode + cat <<- _FTP >> $VSFTPD_CONF +pasv_enable=YES +pasv_max_port=10100 +pasv_min_port=10090 +connect_from_port_20=NO +_FTP + + ${CODE_DIR}/test/restart-ftpd start + fi +} + + +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip + if [ -n "${VSFTPD_CONF}" ]; then + cp ${VSFTPD_CONF}.getssl $VSFTPD_CONF + ${CODE_DIR}/test/restart-ftpd stop + fi +} + + +@test "Use Passive FTP to create challenge file" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + + if [[ ! -d /var/www/html/.well-known/acme-challenge ]]; then + mkdir -p /var/www/html/.well-known/acme-challenge + fi + + # Always change ownership and permissions in case previous tests created the directories as root + chgrp -R www-data /var/www/html/.well-known + chmod -R g+w /var/www/html/.well-known + + CONFIG_FILE="getssl-http01.cfg" + setup_environment + init_getssl + + cat <<- EOF > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +ACL="ftp:ftpuser:ftpuser:${GETSSL_CMD_HOST}:/var/www/html/.well-known/acme-challenge" +EOF + if [[ "$FTP_PASSIVE_DEFAULT" == "false" ]]; then + cat <<- EOF3 >> ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +FTP_OPTIONS="passive" +EOF3 + fi + + create_certificate + assert_success + assert_line --partial "ftp:ftpuser:ftpuser:" + if [[ "$FTP_PASSIVE_DEFAULT" == "false" ]]; then + assert_line --partial "Passive mode on" + else + refute_line --partial "Passive mode off" + fi + check_output_for_errors +} diff --git a/test/35-preferred-chain.bats b/test/35-preferred-chain.bats new file mode 100644 index 0000000..ad94caf --- /dev/null +++ b/test/35-preferred-chain.bats @@ -0,0 +1,116 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + fi +} + + +@test "Use PREFERRED_CHAIN to select an alternate root" { + if [ -n "$STAGING" ]; then + PREFERRED_CHAIN="\(STAGING\) Pretend Pear X1" + CHECK_CHAIN="(STAGING) Pretend Pear X1" + else + PREFERRED_CHAIN=$(curl --silent https://pebble:15000/roots/2 | openssl x509 -text -noout | grep "Issuer:" | awk -F"CN *= *" '{ print $2 }') + PREFERRED_CHAIN="${PREFERRED_CHAIN# }" # remove leading whitespace + CHECK_CHAIN=$PREFERRED_CHAIN + fi + + CONFIG_FILE="getssl-dns01.cfg" + setup_environment + init_getssl + + cat <<- EOF > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +PREFERRED_CHAIN="${PREFERRED_CHAIN}" +EOF + + create_certificate + assert_success + check_output_for_errors + + issuer=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.crt" | openssl pkcs7 -print_certs -text -noout | grep Issuer: | tail -1 | awk -F"CN=" '{ print $2 }') + # verify certificate is issued by preferred chain root + if [[ "${CHECK_CHAIN}" != "$issuer" ]]; then + echo "# PREFERRED_CHAIN=$PREFERRED_CHAIN" + echo "# issuer=$issuer" + fi + + [ "${CHECK_CHAIN}" = "$issuer" ] +} + + +@test "Use PREFERRED_CHAIN to select the default root" { + if [ -n "$STAGING" ]; then + PREFERRED_CHAIN="\(STAGING\) Doctored Durian Root CA X3" + CHECK_CHAIN="(STAGING) Doctored Durian Root CA X3" + else + PREFERRED_CHAIN=$(curl --silent https://pebble:15000/roots/0 | openssl x509 -text -noout | grep Issuer: | awk -F"CN *= *" '{ print $2 }') + PREFERRED_CHAIN="${PREFERRED_CHAIN# }" # remove leading whitespace + CHECK_CHAIN=$PREFERRED_CHAIN + fi + + CONFIG_FILE="getssl-dns01.cfg" + setup_environment + init_getssl + + cat <<- EOF > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +PREFERRED_CHAIN="${PREFERRED_CHAIN}" +EOF + + create_certificate + assert_success + check_output_for_errors + + issuer=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.crt" | openssl pkcs7 -print_certs -text -noout | grep Issuer: | tail -1 | awk -F"CN=" '{ print $2 }') + # verify certificate is issued by preferred chain root + if [[ "${CHECK_CHAIN}" != "$issuer" ]]; then + echo "# PREFERRED_CHAIN=$PREFERRED_CHAIN" + echo "# issuer=$issuer" + fi + [ "${CHECK_CHAIN}" = "$issuer" ] +} + + +@test "Use PREFERRED_CHAIN to select an alternate root by suffix" { + if [ -n "$STAGING" ]; then + FULL_PREFERRED_CHAIN="(STAGING) Pretend Pear X1" + else + FULL_PREFERRED_CHAIN=$(curl --silent https://pebble:15000/roots/2 | openssl x509 -text -noout | grep "Issuer:" | awk -F"CN *= *" '{ print $2 }') + FULL_PREFERRED_CHAIN="${FULL_PREFERRED_CHAIN# }" # remove leading whitespace + fi + + # Take the last word from FULL_PREFERRED_CHAIN as the chain to use + PREFERRED_CHAIN="${FULL_PREFERRED_CHAIN##* }" + CONFIG_FILE="getssl-dns01.cfg" + setup_environment + init_getssl + + cat <<- EOF > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +PREFERRED_CHAIN="${PREFERRED_CHAIN}" +EOF + + create_certificate + assert_success + check_output_for_errors + + issuer=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.crt" | openssl pkcs7 -print_certs -text -noout | grep Issuer: | tail -1 | awk -F"CN=" '{ print $2 }') + # verify certificate is issued by preferred chain root + if [[ "${FULL_PREFERRED_CHAIN}" != "$issuer" ]]; then + echo "# PREFERRED_CHAIN=$PREFERRED_CHAIN" + echo "# FULL_PREFERRED_CHAIN=$FULL_PREFERRED_CHAIN" + echo "# issuer=$issuer" + fi + [ "${FULL_PREFERRED_CHAIN}" = "$issuer" ] +} diff --git a/test/36-full-chain-inc-root.bats b/test/36-full-chain-inc-root.bats new file mode 100644 index 0000000..6f06190 --- /dev/null +++ b/test/36-full-chain-inc-root.bats @@ -0,0 +1,99 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + fi +} + + +@test "Use FULL_CHAIN_INCLUDE_ROOT to include the root certificate in the fullchain" { + CONFIG_FILE="getssl-dns01.cfg" + setup_environment + init_getssl + + cat <<- EOF > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +FULL_CHAIN_INCLUDE_ROOT="true" +EOF + + create_certificate + assert_success + check_output_for_errors + + if [ -n "$STAGING" ]; then + PREFERRED_CHAIN="(STAGING) Doctored Durian Root CA X3" + else + # pebble doesn't support CA Issuers so the fullchain.crt will just contain the certificate (code path means it won't contain the intermediate cert in this case) + # This is testing that requesting FULL_CHAIN_INCLUDE_ROOT doesn't fail if there is no CA Issuers in the certificate + PREFERRED_CHAIN=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt" | openssl pkcs7 -print_certs -text -noout | grep Subject: | tail -1 | awk -F"CN=" '{ print $2 }') + fi + + final_issuer=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.crt" | openssl pkcs7 -print_certs -text -noout | grep Subject: | tail -1 | awk -F"CN=" '{ print $2 }') + + # verify certificate includes the chain root + if [[ "${PREFERRED_CHAIN}" != "$final_issuer" ]]; then + echo "# PREFERRED_CHAIN=$PREFERRED_CHAIN" + echo "# final_issuer=$final_issuer" + fi + [ "${PREFERRED_CHAIN}" = "$final_issuer" ] +} + + +@test "Use FULL_CHAIN_INCLUDE_ROOT with dual certificates" { + if [ -n "$STAGING" ]; then + PREFERRED_CHAIN="(STAGING) Doctored Durian Root CA X3" + fi + + CONFIG_FILE="getssl-dns01.cfg" + setup_environment + init_getssl + + cat <<- EOF > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +FULL_CHAIN_INCLUDE_ROOT="true" +DUAL_RSA_ECDSA="true" +ACCOUNT_KEY_TYPE="prime256v1" +PRIVATE_KEY_ALG="prime256v1" +CHECK_REMOTE="false" +EOF + + create_certificate + assert_success + check_output_for_errors + check_certificates + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/chain.ec.crt" ] + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.ec.crt" ] + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.ec.crt" ] + + if [ -n "$STAGING" ]; then + PREFERRED_CHAIN="(STAGING) Doctored Durian Root CA X3" + else + # pebble doesn't support CA Issuers so the fullchain.crt will just contain the certificate (code path means it won't contain the intermediate cert in this case) + # This is testing that requesting FULL_CHAIN_INCLUDE_ROOT doesn't fail if there is no CA Issuers in the certificate + PREFERRED_CHAIN=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt" | openssl pkcs7 -print_certs -text -noout | grep Subject: | tail -1 | awk -F"CN=" '{ print $2 }') + fi + + # verify both rsa and ecdsa certificates include the chain root + final_issuer=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.crt" | openssl pkcs7 -print_certs -text -noout | grep Subject: | tail -1 | awk -F"CN=" '{ print $2 }') + if [[ "${PREFERRED_CHAIN}" != "$final_issuer" ]]; then + echo "# PREFERRED_CHAIN=$PREFERRED_CHAIN" + echo "# final_issuer=$final_issuer" + fi + [ "${PREFERRED_CHAIN}" = "$final_issuer" ] + ecdsa_final_issuer=$(openssl crl2pkcs7 -nocrl -certfile "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.ec.crt" | openssl pkcs7 -print_certs -text -noout | grep Subject: | tail -1 | awk -F"CN=" '{ print $2 }') + if [[ "$PREFERRED_CHAIN" != "$ecdsa_final_issuer" ]]; then + echo "# PREFERRED_CHAIN=$PREFERRED_CHAIN" + echo "# ecdsa_final_issuer=$ecdsa_final_issuer" + fi + [ "${PREFERRED_CHAIN}" = "$ecdsa_final_issuer" ] +} diff --git a/test/37-idn.bats b/test/37-idn.bats new file mode 100644 index 0000000..27f82e0 --- /dev/null +++ b/test/37-idn.bats @@ -0,0 +1,81 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + +# This is run for every test +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + GETSSL_CMD_HOST=${GETSSL_IDN_HOST} + + # use the test description to move tools we don't want to test out of the way + DNS_TOOL=${BATS_TEST_DESCRIPTION##*:} + for tool in dig drill host nslookup + do + if [[ "$tool" != "$DNS_TOOL" && -f /usr/bin/$tool ]]; then + mv /usr/bin/$tool /usr/bin/${tool}.getssl + fi + done +} + +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip + # use the test description to move tools we didn't want to test back + DNS_TOOL=${BATS_TEST_DESCRIPTION##*-} + for tool in dig drill host nslookup + do + if [[ "$tool" != "$DNS_TOOL" && -f /usr/bin/${tool}.getssl ]]; then + mv /usr/bin/${tool}.getssl /usr/bin/${tool} + fi + done +} + +setup_file() { + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + curl --silent -X POST -d '{"host":"'$GETSSL_IDN_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/add-a + fi +} + +teardown_file() { + if [ -z "$STAGING" ]; then + curl --silent -X POST -d '{"host":"'$GETSSL_IDN_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/clear-a + fi +} + +@test "Check that DNS-01 verification works if the domain is idn:dig" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + + CONFIG_FILE="getssl-dns01.cfg" + + setup_environment + init_getssl + create_certificate + + assert_success + assert_output --partial "dig" + check_output_for_errors +} + +@test "Check that DNS-01 verification works if the domain is idn:drill" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + if [ ! -f /usr/bin/drill ]; then + # Can't find drill package for centos8 / rockylinux8 + skip "Drill not installed on this system" + fi + + CONFIG_FILE="getssl-dns01.cfg" + + setup_environment + init_getssl + create_certificate + + assert_success + assert_output --partial "drill" + check_output_for_errors +} diff --git a/test/38-idn-http01-check-noidnout.bats b/test/38-idn-http01-check-noidnout.bats new file mode 100644 index 0000000..01b7542 --- /dev/null +++ b/test/38-idn-http01-check-noidnout.bats @@ -0,0 +1,49 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + GETSSL_CMD_HOST=$GETSSL_IDN_HOST +} + +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + +setup_file() { + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + curl --silent -X POST -d '{"host":"'$GETSSL_IDN_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/add-a + fi +} + +teardown_file() { + if [ -z "$STAGING" ]; then + curl --silent -X POST -d '{"host":"'$GETSSL_IDN_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/clear-a + + fi +} + +@test "Ensure noidnout in check_config isn't passed to host and nslookup (HTTP-01)" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + CONFIG_FILE="getssl-http01.cfg" + setup_environment + init_getssl + cat <<- EOF > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +SANS="${GETSSL_HOST}" +USE_SINGLE_ACL="true" +EOF + + create_certificate --check-config + + assert_success + refute_output --partial "DNS lookup using host +noidnout" + refute_output --partial "DNS lookup using nslookup +noidnout" + refute_output --partial "+noidnout $GETSSL_HOST" + check_output_for_errors +} diff --git a/test/39-private-key-alg-changed.bats b/test/39-private-key-alg-changed.bats new file mode 100644 index 0000000..a8b0e43 --- /dev/null +++ b/test/39-private-key-alg-changed.bats @@ -0,0 +1,88 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt +} + +teardown_file() { + cleanup_environment +} + +@test "Create new certificate to create a private key" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + CONFIG_FILE="getssl-http01.cfg" + setup_environment + init_getssl + create_certificate + assert_success + check_output_for_errors + # save a coy of the private key + cp "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.key" "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.key.orig" +} + +@test "Renew certificate (not force) and check nothing happens and key doesn't change" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + + ORIG_KEY_HASH="$(cat ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.key | sha256sum)" + + run ${CODE_DIR}/getssl -U -d $GETSSL_HOST + assert_success + assert_line --partial "certificate is valid for more than 30 days" + check_output_for_errors + + NEW_KEY_HASH="$(cat ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.key | sha256sum)" + + assert [ "$NEW_KEY_HASH" == "$ORIG_KEY_HASH" ] +} + +@test "Force renewal and check key hasn't changed" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + ORIG_KEY_HASH="$(cat ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.key | sha256sum)" + + run ${CODE_DIR}/getssl -U -d -f $GETSSL_HOST + assert_success + check_output_for_errors + + NEW_KEY_HASH="$(cat ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.key | sha256sum)" + + assert [ "$NEW_KEY_HASH" == "$ORIG_KEY_HASH" ] +} + +@test "Change key algorithm, force renewal, and check key has changed" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + + ORIG_KEY_HASH="$(cat ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.key | sha256sum)" + + cat <<- 'EOF' > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +PRIVATE_KEY_ALG="prime256v1" +EOF + + run ${CODE_DIR}/getssl -U -d $GETSSL_HOST + assert_success + refute_line --partial "certificate is valid for more than 30 days" + + check_output_for_errors + + NEW_KEY_HASH="$(cat ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.key | sha256sum)" + + assert [ "$NEW_KEY_HASH" != "$ORIG_KEY_HASH" ] +} diff --git a/test/4-more-than-10-hosts.bats b/test/4-more-than-10-hosts.bats index bd93adc..efa1ea0 100644 --- a/test/4-more-than-10-hosts.bats +++ b/test/4-more-than-10-hosts.bats @@ -6,8 +6,34 @@ load '/getssl/test/test_helper.bash' # This is run for every test +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + + setup() { - export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" +} + + +setup_file() { + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + # Add 11 hosts to DNS (also need to be added as aliases in docker-compose.yml) + for prefix in a b c d e f g h i j k; do + curl --silent -X POST -d '{"host":"'$prefix.$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/add-a + done + fi +} + + +teardown_file() { + # Remove all the dns aliases + if [ -n "$STAGING" ]; then + for prefix in a b c d e f g h i j k; do + curl --silent -X POST -d '{"host":"'$prefix.$GETSSL_HOST'"}' http://10.30.50.3:8055/clear-a + done + fi } @@ -18,17 +44,10 @@ setup() { CONFIG_FILE="getssl-http01-10-hosts.cfg" setup_environment - # Add 11 hosts to DNS (also need to be added as aliases in docker-compose.yml) - for prefix in a b c d e f g h i j k; do - curl --silent -X POST -d '{"host":"'$prefix.$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/add-a - done - init_getssl create_certificate assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + check_output_for_errors } @@ -36,14 +55,8 @@ setup() { if [ -n "$STAGING" ]; then skip "Using staging server, skipping internal test" fi - run ${CODE_DIR}/getssl -f $GETSSL_HOST + run ${CODE_DIR}/getssl -U -d -f $GETSSL_HOST assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' - # Remove all the dns aliases + check_output_for_errors cleanup_environment - for prefix in a b c d e f g h i j k; do - curl --silent -X POST -d '{"host":"'$prefix.$GETSSL_HOST'"}' http://10.30.50.3:8055/clear-a - done } diff --git a/test/5-secp384-http01.bats b/test/5-secp384-http01.bats index 29da2da..8a1de37 100644 --- a/test/5-secp384-http01.bats +++ b/test/5-secp384-http01.bats @@ -6,7 +6,12 @@ load '/getssl/test/test_helper.bash' # This is run for every test +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt } @@ -20,6 +25,7 @@ setup() { init_getssl create_certificate assert_success + check_output_for_errors } @@ -27,8 +33,9 @@ setup() { if [ -n "$STAGING" ]; then skip "Using staging server, skipping internal test" fi - run ${CODE_DIR}/getssl -f $GETSSL_HOST + run ${CODE_DIR}/getssl -U -d -f $GETSSL_HOST assert_success + check_output_for_errors } @@ -41,6 +48,7 @@ setup() { init_getssl create_certificate assert_success + check_output_for_errors } @@ -48,6 +56,7 @@ setup() { if [ -n "$STAGING" ]; then skip "Using staging server, skipping internal test" fi - run ${CODE_DIR}/getssl -f $GETSSL_HOST + run ${CODE_DIR}/getssl -U -d -f $GETSSL_HOST assert_success + check_output_for_errors } diff --git a/test/6-dual-rsa-ecdsa-copy-2-locations.bats b/test/6-dual-rsa-ecdsa-copy-2-locations.bats index 73363ec..1ad73f4 100644 --- a/test/6-dual-rsa-ecdsa-copy-2-locations.bats +++ b/test/6-dual-rsa-ecdsa-copy-2-locations.bats @@ -7,6 +7,16 @@ load '/getssl/test/test_helper.bash' # These are run for every test, not once per file setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" +} + + +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + + +setup_file() { if [ -z "$STAGING" ]; then export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt curl --silent -X POST -d '{"host":"'a.$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/add-a @@ -14,7 +24,7 @@ setup() { } -teardown() { +teardown_file() { if [ -z "$STAGING" ]; then curl --silent -X POST -d '{"host":"'a.$GETSSL_HOST'"}' http://10.30.50.3:8055/clear-a fi @@ -25,13 +35,25 @@ teardown() { if [ -n "$STAGING" ]; then skip "Using staging server, skipping internal test" fi - CONFIG_FILE="getssl-http01-dual-rsa-ecdsa-2-locations.cfg" + + check_nginx + if [ "$OLD_NGINX" = "false" ]; then + CONFIG_FILE="getssl-http01-dual-rsa-ecdsa-2-locations.cfg" + else + CONFIG_FILE="getssl-http01-dual-rsa-ecdsa-2-locations-old-nginx.cfg" + fi + setup_environment mkdir -p /root/a.${GETSSL_HOST} init_getssl create_certificate assert_success + check_output_for_errors + if [ "$OLD_NGINX" = "false" ]; then + assert_line --partial "rsa certificate installed OK on server" + assert_line --partial "prime256v1 certificate installed OK on server" + fi # Check that the RSA chain and key have been copied to both locations assert [ -e "/etc/nginx/pki/domain-chain.crt" ] @@ -45,3 +67,25 @@ teardown() { assert [ -e "/etc/nginx/pki/private/server.ec.key" ] assert [ -e "/root/a.${GETSSL_HOST}/server.ec.key" ] } + + +@test "Create dual certificates and copy to two locations but not returned by server" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + + check_nginx + if [ "$OLD_NGINX" = "false" ]; then + CONFIG_FILE="getssl-http01-dual-rsa-ecdsa-2-locations-wrong-nginx.cfg" + else + skip "Skipping as old nginx servers cannot return both certificates" + fi + + setup_environment + mkdir -p /root/a.${GETSSL_HOST} + + init_getssl + create_certificate + assert_failure + assert_line --partial "prime256v1 certificate obtained but not installed on server" +} diff --git a/test/7-duckdns-dns01.bats b/test/7-duckdns-dns01.bats deleted file mode 100644 index 0c680ea..0000000 --- a/test/7-duckdns-dns01.bats +++ /dev/null @@ -1,34 +0,0 @@ -#! /usr/bin/env bats - -load '/bats-support/load.bash' -load '/bats-assert/load.bash' -load '/getssl/test/test_helper.bash' - - - -@test "Create new certificate using staging server and DuckDNS" { - if [ -z "$STAGING" ]; then - skip "Running internal tests, skipping external test" - fi - CONFIG_FILE="getssl-duckdns01.cfg" - - setup_environment - init_getssl - create_certificate - assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' -} - -@test "Force renewal of certificate using staging server and DuckDNS" { - if [ -z "$STAGING" ]; then - skip "Running internal tests, skipping external test" - fi - run ${CODE_DIR}/getssl -f $GETSSL_HOST - assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[Ee][Rr][Rr][Oo][Rr]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' - cleanup_environment -} diff --git a/test/7-test-renewal.bats b/test/7-test-renewal.bats new file mode 100644 index 0000000..30a5472 --- /dev/null +++ b/test/7-test-renewal.bats @@ -0,0 +1,107 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" +} + + +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + + +setup_file() { + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + curl --silent -X POST -d '{"host":"a.'$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/add-a + curl --silent -X POST -d '{"host":"b.'$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/add-a + fi +} + + +teardown_file() { + if [ -z "$STAGING" ]; then + curl --silent -X POST -d '{"host":"a.'$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/clear-a + curl --silent -X POST -d '{"host":"b.'$GETSSL_HOST'", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/clear-a + fi +} + + +@test "Create certificate to check renewal" { + if [ -n "$STAGING" ]; then + skip "Not testing renewal on staging server" + fi + CONFIG_FILE="getssl-dns01.cfg" + setup_environment + init_getssl + + create_certificate + assert_success + check_output_for_errors +} + + +@test "Check that trying to renew a certificate which doesn't need renewing doesn't do anything" { + if [ -n "$STAGING" ]; then + skip "Not trying on staging server yet" + fi + + CONFIG_FILE="getssl-dns01.cfg" + . "${CODE_DIR}/test/test-config/${CONFIG_FILE}" + CERT=${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt + ORIGINAL_ENDDATE=$(openssl x509 -in "$CERT" -noout -enddate 2>/dev/null| cut -d= -f 2-) + + create_certificate + assert_success + check_output_for_errors + + # Check that getssl didn't renew the certificate + refute_line --partial "certificate needs renewal" + assert_line --partial 'certificate is valid for more than' + + # Check that the end date in the certificate hasn't changed + UPDATED_ENDDATE=$(openssl x509 -in "$CERT" -noout -enddate 2>/dev/null| cut -d= -f 2-) + if [[ "$ORIGINAL_ENDDATE" != "$UPDATED_ENDDATE" ]]; then + echo "# ORIGINAL_ENDDATE=$ORIGINAL_ENDDATE" + echo "# UPDATED_ENDDATE =$UPDATED_ENDDATE" + fi + [[ "$ORIGINAL_ENDDATE" = "$UPDATED_ENDDATE" ]] +} + + + +@test "Check that we can renew a certificate which does need renewing" { + if [ -n "$STAGING" ]; then + skip "Not trying on staging server yet" + fi + + CONFIG_FILE="getssl-dns01.cfg" + cat <<- EOF > ${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl_test_specific.cfg +RENEW_ALLOW=2000 +EOF + + . "${CODE_DIR}/test/test-config/${CONFIG_FILE}" + CERT=${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt + ORIGINAL_ENDDATE=$(openssl x509 -in "$CERT" -noout -enddate 2>/dev/null| cut -d= -f 2-) + + create_certificate + assert_success + check_output_for_errors + + # Check that getssl didn't renew the certificate + refute_line --partial 'certificate is valid for more than' + + # Check that the end date in the certificate hasn't changed + UPDATED_ENDDATE=$(openssl x509 -in "$CERT" -noout -enddate 2>/dev/null| cut -d= -f 2-) + if [[ "$ORIGINAL_ENDDATE" = "$UPDATED_ENDDATE" ]]; then + echo "# ORIGINAL_ENDDATE=$ORIGINAL_ENDDATE" + echo "# UPDATED_ENDDATE =$UPDATED_ENDDATE" + fi + [[ "$ORIGINAL_ENDDATE" != "$UPDATED_ENDDATE" ]] +} diff --git a/test/8-duckdns-ecdsa.bats b/test/8-duckdns-ecdsa.bats deleted file mode 100644 index dfe84fe..0000000 --- a/test/8-duckdns-ecdsa.bats +++ /dev/null @@ -1,70 +0,0 @@ -#! /usr/bin/env bats - -load '/bats-support/load.bash' -load '/bats-assert/load.bash' -load '/getssl/test/test_helper.bash' - - - - -@test "Create new certificate using staging server and prime256v1" { - if [ -z "$STAGING" ]; then - skip "Running internal tests, skipping external test" - fi - CONFIG_FILE="getssl-duckdns01.cfg" - - setup_environment - init_getssl - sed -e 's/rsa/prime256v1/g' < "${CODE_DIR}/test/test-config/${CONFIG_FILE}" > "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" - run ${CODE_DIR}/getssl -d "$GETSSL_HOST" - assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' -} - - -@test "Force renewal of certificate using staging server and prime256v1" { - if [ -z "$STAGING" ]; then - skip "Running internal tests, skipping external test" - fi - run ${CODE_DIR}/getssl -d -f $GETSSL_HOST - assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' - cleanup_environment -} - - -@test "Create new certificate using staging server and secp384r1" { - if [ -z "$STAGING" ]; then - skip "Running internal tests, skipping external test" - fi - CONFIG_FILE="getssl-duckdns01.cfg" - - setup_environment - init_getssl - sed -e 's/rsa/secp384r1/g' < "${CODE_DIR}/test/test-config/${CONFIG_FILE}" > "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" - run ${CODE_DIR}/getssl -d "$GETSSL_HOST" - assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' -} - - -@test "Force renewal of certificate using staging server and secp384r1" { - if [ -z "$STAGING" ]; then - skip "Running internal tests, skipping external test" - fi - run ${CODE_DIR}/getssl -d -f $GETSSL_HOST - assert_success - refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' - refute_output --regexp '[^:][Ee][Rr][Rr][Oo][Rr][^:]' - refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' - cleanup_environment -} - - -# Note letsencrypt doesn't support ECDSA curve P-521 as it's being deprecated diff --git a/test/8-staging-ecdsa.bats b/test/8-staging-ecdsa.bats new file mode 100644 index 0000000..c486ea7 --- /dev/null +++ b/test/8-staging-ecdsa.bats @@ -0,0 +1,70 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" +} + + +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + + +@test "Create new certificate using staging server and prime256v1" { + if [ -z "$STAGING" ]; then + skip "Running local tests this is a staging server test" + fi + CONFIG_FILE="getssl-dns01.cfg" + + setup_environment + init_getssl + sed -e 's/rsa/prime256v1/g' < "${CODE_DIR}/test/test-config/${CONFIG_FILE}" > "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" + run ${CODE_DIR}/getssl -U -d "$GETSSL_HOST" + assert_success + check_output_for_errors +} + + +@test "Force renewal of certificate using staging server and prime256v1" { + if [ -z "$STAGING" ]; then + skip "Running local tests this is a staging server test" + fi + run ${CODE_DIR}/getssl -U -d -f $GETSSL_HOST + assert_success + check_output_for_errors + cleanup_environment +} + + +@test "Create new certificate using staging server and secp384r1" { + if [ -z "$STAGING" ]; then + skip "Running local tests this is a staging server test" + fi + CONFIG_FILE="getssl-dns01.cfg" + + setup_environment + init_getssl + sed -e 's/rsa/secp384r1/g' < "${CODE_DIR}/test/test-config/${CONFIG_FILE}" > "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" + run ${CODE_DIR}/getssl -U -d "$GETSSL_HOST" + assert_success + check_output_for_errors +} + + +@test "Force renewal of certificate using staging server and secp384r1" { + if [ -z "$STAGING" ]; then + skip "Running local tests this is a staging server test" + fi + run ${CODE_DIR}/getssl -U -d -f $GETSSL_HOST + assert_success + check_output_for_errors + cleanup_environment +} + + +# Note letsencrypt doesn't support ECDSA curve P-521 as it's being deprecated diff --git a/test/9-multiple-domains-dns01.bats b/test/9-multiple-domains-dns01.bats new file mode 100644 index 0000000..e693c3e --- /dev/null +++ b/test/9-multiple-domains-dns01.bats @@ -0,0 +1,73 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" +} + +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + + +setup_file() { + # Add top level domain from SANS to DNS + if [ -z "$STAGING" ]; then + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + curl --silent -X POST -d '{"host":"getssl.test", "addresses":["'$GETSSL_IP'"]}' http://10.30.50.3:8055/add-a + fi +} + + +teardown_file() { + if [ -z "$STAGING" ]; then + curl --silent -X POST -d '{"host":"getssl.tst"}' http://10.30.50.3:8055/clear-a + fi +} + + +@test "Create certificates for multi-level domains using DNS-01 verification" { + # This tests we can create a certificate for .getssl.test and getssl.test (in SANS) + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + CONFIG_FILE="getssl-dns01-multiple-domains.cfg" + setup_environment + + init_getssl + create_certificate + assert_success + check_output_for_errors +} + + +@test "Force renewal of multi-level domains using DNS-01" { + # This tests we can renew a certificate for .getssl.test and getssl.test (in SANS) + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + run ${CODE_DIR}/getssl -U -d -f $GETSSL_HOST + assert_success + check_output_for_errors + cleanup_environment +} + + +@test "Test IGNORE_DIRECTORY_DOMAIN using DNS-01 verification" { + # This tests we can create a certificate for getssl.test and .getssl.test (*both* in SANS) + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + CONFIG_FILE="getssl-dns01-ignore-directory-domain.cfg" + setup_environment + + init_getssl + create_certificate + assert_success + check_output_for_errors +} diff --git a/test/9-test--all.bats b/test/9-test--all.bats new file mode 100644 index 0000000..7e317cc --- /dev/null +++ b/test/9-test--all.bats @@ -0,0 +1,37 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt + export PATH=$PATH:/getssl +} + + +@test "Create new certificate using --all" { + if [ -n "$STAGING" ]; then + skip "Using staging server, skipping internal test" + fi + + # Setup + CONFIG_FILE="getssl-http01.cfg" + setup_environment + init_getssl + cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" + + # Run test + run ${CODE_DIR}/getssl -U -d --all + + # Check success conditions + assert_success + check_output_for_errors +} diff --git a/test/Dockerfile-alpine b/test/Dockerfile-alpine index 0c166cb..667512e 100644 --- a/test/Dockerfile-alpine +++ b/test/Dockerfile-alpine @@ -2,21 +2,32 @@ FROM alpine:latest # Note this image uses busybox awk instead of gawk -RUN apk --no-cache add supervisor openssl git curl bind-tools wget nginx bash +RUN apk --no-cache add supervisor openssl git curl bind-tools drill wget nginx bash lftp vsftpd openssh-server jq WORKDIR /root # Create nginx directories in standard places -RUN mkdir /run/nginx -RUN mkdir /etc/nginx/pki -RUN mkdir /etc/nginx/pki/private +RUN mkdir -p /run/nginx +RUN mkdir -p /etc/nginx/pki/private + +# Setup ftp +ENV VSFTPD_CONF=/etc/vsftpd.conf +ENV FTP_PASSIVE_DEFAULT=true +COPY ./test/test-config/vsftpd.conf /etc/vsftpd.conf +RUN echo "seccomp_sandbox=NO" >> /etc/vsftpd.conf +RUN adduser -D ftpuser +RUN echo 'ftpuser:ftpuser' | chpasswd +RUN adduser ftpuser www-data +RUN adduser root www-data +RUN chown -R ftpuser.www-data /var/www +RUN chmod g+w -R /var/www # BATS (Bash Automated Testings) -RUN git clone https://github.com/bats-core/bats-core.git /bats-core -RUN git clone https://github.com/jasonkarns/bats-support /bats-support -RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone --depth 1 https://github.com/bats-core/bats-support /bats-support +RUN git clone --depth 1 https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local # Use supervisord to run nginx in the background COPY ./test/test-config/alpine-supervisord.conf /etc/supervisord.conf -CMD tail -f /dev/null +CMD [ "tail", "-f", "/dev/null" ] diff --git a/test/Dockerfile-bash4-0 b/test/Dockerfile-bash4-0 new file mode 100644 index 0000000..8c92f8e --- /dev/null +++ b/test/Dockerfile-bash4-0 @@ -0,0 +1,34 @@ +FROM bash:4.0 + +# https://hub.docker.com/_/bash + +RUN apk --no-cache add supervisor openssl git curl bind-tools drill wget nginx lftp vsftpd openssh-server jq + +WORKDIR /root + +# Create nginx directories in standard places +RUN mkdir -p /run/nginx +RUN mkdir -p /etc/nginx/pki +RUN mkdir -p /etc/nginx/pki/private + +# Setup ftp +ENV VSFTPD_CONF=/etc/vsftpd.conf +ENV FTP_PASSIVE_DEFAULT=true +COPY ./test/test-config/vsftpd.conf /etc/vsftpd.conf +RUN echo "seccomp_sandbox=NO" >> /etc/vsftpd.conf +RUN adduser -D ftpuser +RUN echo 'ftpuser:ftpuser' | chpasswd +RUN adduser ftpuser www-data +RUN adduser root www-data +RUN chown -R ftpuser.www-data /var/www +RUN chmod g+w -R /var/www + +# BATS (Bash Automated Testings) +RUN git clone https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone https://github.com/bats-core/bats-support /bats-support +RUN git clone https://github.com/bats-core/bats-assert /bats-assert +RUN /bats-core/install.sh /usr/local + +# Use supervisord to run nginx in the background +COPY ./test/test-config/alpine-supervisord.conf /etc/supervisord.conf +CMD tail -f /dev/null diff --git a/test/Dockerfile-bash4-2 b/test/Dockerfile-bash4-2 new file mode 100644 index 0000000..1f0f7f3 --- /dev/null +++ b/test/Dockerfile-bash4-2 @@ -0,0 +1,34 @@ +FROM bash:4.2 + +# https://hub.docker.com/_/bash + +RUN apk --no-cache add supervisor openssl git curl bind-tools drill wget nginx lftp vsftpd openssh-server jq + +WORKDIR /root + +# Create nginx directories in standard places +RUN mkdir -p /run/nginx +RUN mkdir -p /etc/nginx/pki +RUN mkdir -p /etc/nginx/pki/private + +# Setup ftp +ENV VSFTPD_CONF=/etc/vsftpd.conf +ENV FTP_PASSIVE_DEFAULT=true +COPY ./test/test-config/vsftpd.conf /etc/vsftpd.conf +RUN echo "seccomp_sandbox=NO" >> /etc/vsftpd.conf +RUN adduser -D ftpuser +RUN echo 'ftpuser:ftpuser' | chpasswd +RUN adduser ftpuser www-data +RUN adduser root www-data +RUN chown -R ftpuser.www-data /var/www +RUN chmod g+w -R /var/www + +# BATS (Bash Automated Testings) +RUN git clone https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone https://github.com/bats-core/bats-support /bats-support +RUN git clone https://github.com/bats-core/bats-assert /bats-assert +RUN /bats-core/install.sh /usr/local + +# Use supervisord to run nginx in the background +COPY ./test/test-config/alpine-supervisord.conf /etc/supervisord.conf +CMD tail -f /dev/null diff --git a/test/Dockerfile-bash5-0 b/test/Dockerfile-bash5-0 new file mode 100644 index 0000000..a437388 --- /dev/null +++ b/test/Dockerfile-bash5-0 @@ -0,0 +1,34 @@ +FROM bash:5.0 + +# https://hub.docker.com/_/bash + +RUN apk --no-cache add supervisor openssl git curl bind-tools drill wget nginx lftp vsftpd openssh-server jq + +WORKDIR /root + +# Create nginx directories in standard places +RUN mkdir -p /run/nginx +RUN mkdir -p /etc/nginx/pki +RUN mkdir -p /etc/nginx/pki/private + +# Setup ftp +ENV VSFTPD_CONF=/etc/vsftpd.conf +ENV FTP_PASSIVE_DEFAULT=true +COPY ./test/test-config/vsftpd.conf /etc/vsftpd.conf +RUN echo "seccomp_sandbox=NO" >> /etc/vsftpd.conf +RUN adduser -D ftpuser +RUN echo 'ftpuser:ftpuser' | chpasswd +RUN adduser ftpuser www-data +RUN adduser root www-data +RUN chown -R ftpuser.www-data /var/www +RUN chmod g+w -R /var/www + +# BATS (Bash Automated Testings) +RUN git clone https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone https://github.com/bats-core/bats-support /bats-support +RUN git clone https://github.com/bats-core/bats-assert /bats-assert +RUN /bats-core/install.sh /usr/local + +# Use supervisord to run nginx in the background +COPY ./test/test-config/alpine-supervisord.conf /etc/supervisord.conf +CMD tail -f /dev/null diff --git a/test/Dockerfile-centos6 b/test/Dockerfile-centos6 index 9149dad..f8d08b2 100644 --- a/test/Dockerfile-centos6 +++ b/test/Dockerfile-centos6 @@ -1,24 +1,47 @@ FROM centos:centos6 # Note this image uses gawk +# Note if you are running this using WSL2 you need to put the following lines in %userprofile%\.wslconfig +# [wsl2] +# kernelCommandLine = vsyscall=emulate + +# Centos 6 is EOL and is no longer available from the usual mirrors, so switch to https://vault.centos.org +RUN sed -i 's/enabled=1/enabled=0/g' /etc/yum/pluginconf.d/fastestmirror.conf && \ + sed -i 's/^mirrorlist/#mirrorlist/g' /etc/yum.repos.d/*.repo && \ + sed -i 's;^#baseurl=http://mirror;baseurl=https://vault;g' /etc/yum.repos.d/*.repo # Update and install required software -RUN yum -y update RUN yum -y install epel-release -RUN yum -y install git curl dnsutils wget nginx +RUN yum -y install git curl dnsutils ldns wget nginx jq +RUN yum -y install ftp vsftpd +RUN yum -y install openssh-server + +# Setup ftp +ENV VSFTPD_CONF=/etc/vsftpd/vsftpd.conf +ENV FTP_PASSIVE_DEFAULT=true +COPY test/test-config/vsftpd.conf /etc/vsftpd/vsftpd.conf +RUN adduser ftpuser +RUN echo 'ftpuser:ftpuser' | chpasswd +RUN adduser www-data +RUN usermod -G www-data ftpuser +RUN usermod -G www-data root +RUN mkdir -p /var/www/.well-known/acme-challenge +RUN chown -R www-data.www-data /var/www +RUN chmod g+w -R /var/www WORKDIR /root -RUN mkdir /etc/nginx/pki -RUN mkdir /etc/nginx/pki/private +RUN mkdir -p /etc/nginx/pki/private COPY ./test/test-config/nginx-ubuntu-no-ssl /etc/nginx/conf.d/default.conf # BATS (Bash Automated Testings) -RUN git clone https://github.com/bats-core/bats-core.git /bats-core -RUN git clone https://github.com/jasonkarns/bats-support /bats-support -RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN git clone https://github.com/bats-core/bats-core.git /bats-core # --branch v1.2.1 +RUN git clone https://github.com/bats-core/bats-support /bats-support +RUN git clone https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local +# Hack to disable BATS pretty formatter which stopped working on centos6 +ENV CI=yes EXPOSE 80 443 # Run eternal loop - for testing -CMD tail -f /dev/null +CMD [ "tail", "-f", "/dev/null" ] diff --git a/test/Dockerfile-centos7 b/test/Dockerfile-centos7 new file mode 100644 index 0000000..400dcea --- /dev/null +++ b/test/Dockerfile-centos7 @@ -0,0 +1,37 @@ +FROM centos:centos7 + +# Update and install required software +RUN yum -y update +RUN yum -y install epel-release +RUN yum -y install git curl ldns bind-utils wget which nginx jq +RUN yum -y install ftp vsftpd +RUN yum -y install openssh-server + +# Set locale +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 + +WORKDIR /root +RUN mkdir -p /etc/nginx/pki/private +COPY ./test/test-config/nginx-ubuntu-no-ssl /etc/nginx/conf.d/default.conf +COPY ./test/test-config/nginx-centos7.conf /etc/nginx/nginx.conf + +# Setup ftp +ENV VSFTPD_CONF=/etc/vsftpd/vsftpd.conf +ENV FTP_PASSIVE_DEFAULT=true +COPY test/test-config/vsftpd.conf /etc/vsftpd/vsftpd.conf +RUN adduser ftpuser +RUN echo 'ftpuser:ftpuser' | chpasswd +RUN adduser www-data +RUN usermod -G www-data ftpuser +RUN usermod -G www-data root +RUN mkdir -p /var/www/.well-known/acme-challenge +RUN chown -R www-data.www-data /var/www +RUN chmod g+w -R /var/www + +# BATS (Bash Automated Testings) +RUN git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone --depth 1 https://github.com/bats-core/bats-support /bats-support +RUN git clone --depth 1 https://github.com/bats-core/bats-assert /bats-assert +RUN /bats-core/install.sh /usr/local diff --git a/test/Dockerfile-centos7-duckdns b/test/Dockerfile-centos7-duckdns index 839ff76..46bd254 100644 --- a/test/Dockerfile-centos7-duckdns +++ b/test/Dockerfile-centos7-duckdns @@ -5,24 +5,29 @@ FROM centos:centos7 # Update and install required software RUN yum -y update RUN yum -y install epel-release -RUN yum -y install git curl bind-utils wget which nginx +RUN yum -y install git curl bind-utils ldns wget which nginx jq + +# Set locale +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 ENV staging "true" -ENV DUCKDNS_TOKEN 1d616aa9-b8e4-4bb4-b312-3289de82badb +ENV dynamic_dns "dynu" +#ENV DUCKDNS_TOKEN WORKDIR /root -RUN mkdir /etc/nginx/pki -RUN mkdir /etc/nginx/pki/private +RUN mkdir -p /etc/nginx/pki/private COPY ./test/test-config/nginx-ubuntu-no-ssl /etc/nginx/conf.d/default.conf COPY ./test/test-config/nginx-centos7.conf /etc/nginx/nginx.conf # BATS (Bash Automated Testings) -RUN git clone https://github.com/bats-core/bats-core.git /bats-core -RUN git clone https://github.com/jasonkarns/bats-support /bats-support -RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone --depth 1 https://github.com/bats-core/bats-support /bats-support +RUN git clone --depth 1 https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local EXPOSE 80 443 # Run eternal loop - for testing -CMD tail -f /dev/null +CMD [ "tail", "-f", "/dev/null" ] diff --git a/test/Dockerfile-centos7-dynu b/test/Dockerfile-centos7-dynu new file mode 100644 index 0000000..f196c5d --- /dev/null +++ b/test/Dockerfile-centos7-dynu @@ -0,0 +1,34 @@ +FROM centos:centos7 + +# Note this image uses gawk + +# Update and install required software +RUN yum -y update +RUN yum -y install epel-release +RUN yum -y install git curl bind-utils ldns wget which nginx jq + +# Set locale +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 + +ENV staging "true" +ENV dynamic_dns "duckdns" +#ENV DYNU_API_KEY + +WORKDIR /root +RUN mkdir -p /etc/nginx/pki +RUN mkdir -p /etc/nginx/pki/private +COPY ./test/test-config/nginx-ubuntu-no-ssl /etc/nginx/conf.d/default.conf +COPY ./test/test-config/nginx-centos7.conf /etc/nginx/nginx.conf + +# BATS (Bash Automated Testings) +RUN git clone https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone https://github.com/bats-core/bats-support /bats-support +RUN git clone https://github.com/bats-core/bats-assert /bats-assert +RUN /bats-core/install.sh /usr/local + +EXPOSE 80 443 + +# Run eternal loop - for testing +CMD tail -f /dev/null diff --git a/test/Dockerfile-centos8 b/test/Dockerfile-centos8 new file mode 100644 index 0000000..dc6853b --- /dev/null +++ b/test/Dockerfile-centos8 @@ -0,0 +1,40 @@ +FROM centos:centos8 + +# Note this image does not have drill + +# Update and install required software +RUN yum -y update +RUN yum -y install glibc-all-langpacks +RUN yum -y install epel-release +RUN yum -y install git curl bind-utils wget which nginx jq +RUN yum -y install ftp vsftpd +RUN yum -y install openssh-server + +# Set locale +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 + +WORKDIR /root +RUN mkdir -p /etc/nginx/pki/private +COPY ./test/test-config/nginx-ubuntu-no-ssl /etc/nginx/conf.d/default.conf +COPY ./test/test-config/nginx-centos7.conf /etc/nginx/nginx.conf + +# Setup ftp +ENV VSFTPD_CONF=/etc/vsftpd/vsftpd.conf +ENV FTP_PASSIVE_DEFAULT=true +COPY test/test-config/vsftpd.conf /etc/vsftpd/vsftpd.conf +RUN adduser ftpuser +RUN echo 'ftpuser:ftpuser' | chpasswd +RUN adduser www-data +RUN usermod -G www-data ftpuser +RUN usermod -G www-data root +RUN mkdir -p /var/www/.well-known/acme-challenge +RUN chown -R www-data.www-data /var/www +RUN chmod g+w -R /var/www + +# BATS (Bash Automated Testings) +RUN git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone --depth 1 https://github.com/bats-core/bats-support /bats-support +RUN git clone --depth 1 https://github.com/bats-core/bats-assert /bats-assert +RUN /bats-core/install.sh /usr/local diff --git a/test/Dockerfile-debian b/test/Dockerfile-debian index 95ebbac..0deedff 100644 --- a/test/Dockerfile-debian +++ b/test/Dockerfile-debian @@ -4,17 +4,36 @@ FROM debian:latest # Update and install required software RUN apt-get update --fix-missing -RUN apt-get install -y git curl dnsutils wget nginx-light +RUN apt-get install -y git curl dnsutils ldnsutils wget nginx-light jq +RUN apt-get install -y ftp vsftpd +RUN apt-get install -y openssh-server +RUN apt-get install -y locales # for idn testing + +# Set locale +RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 WORKDIR /root -RUN mkdir /etc/nginx/pki -RUN mkdir /etc/nginx/pki/private +RUN mkdir -p /etc/nginx/pki/private + +# Setup ftp +ENV VSFTPD_CONF=/etc/vsftpd.conf +ENV FTP_PASSIVE_DEFAULT=false +COPY test/test-config/vsftpd.conf /etc/vsftpd.conf +RUN adduser ftpuser +RUN echo 'ftpuser:ftpuser' | chpasswd +RUN adduser ftpuser www-data +RUN adduser root www-data +RUN chown -R www-data.www-data /var/www +RUN chmod g+w -R /var/www # BATS (Bash Automated Testings) -RUN git clone https://github.com/bats-core/bats-core.git /bats-core -RUN git clone https://github.com/jasonkarns/bats-support /bats-support -RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone --depth 1 https://github.com/bats-core/bats-support /bats-support +RUN git clone --depth 1 https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local # Run eternal loop - for testing -CMD tail -f /dev/null +CMD [ "tail", "-f", "/dev/null" ] diff --git a/test/Dockerfile-rockylinux8 b/test/Dockerfile-rockylinux8 new file mode 100644 index 0000000..37bff04 --- /dev/null +++ b/test/Dockerfile-rockylinux8 @@ -0,0 +1,38 @@ +FROM rockylinux/rockylinux:8 + +# Update and install required software +RUN yum -y update +RUN yum -y install epel-release +RUN yum -y install git curl bind-utils wget which nginx jq +RUN yum -y install ftp vsftpd +RUN yum -y install openssh-server +RUN yum -y install glibc-locale-source glibc-langpack-en # for en_US.UTF-8 support + +# Set locale +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 + +WORKDIR /root +RUN mkdir -p /etc/nginx/pki/private +COPY ./test/test-config/nginx-ubuntu-no-ssl /etc/nginx/conf.d/default.conf +COPY ./test/test-config/nginx-centos7.conf /etc/nginx/nginx.conf + +# Setup ftp +ENV VSFTPD_CONF=/etc/vsftpd/vsftpd.conf +ENV FTP_PASSIVE_DEFAULT=true +COPY test/test-config/vsftpd.conf /etc/vsftpd/vsftpd.conf +RUN adduser ftpuser +RUN echo 'ftpuser:ftpuser' | chpasswd +RUN adduser www-data +RUN usermod -G www-data ftpuser +RUN usermod -G www-data root +RUN mkdir -p /var/www/.well-known/acme-challenge +RUN chown -R www-data.www-data /var/www +RUN chmod g+w -R /var/www + +# BATS (Bash Automated Testings) +RUN git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone --depth 1 https://github.com/bats-core/bats-support /bats-support +RUN git clone --depth 1 https://github.com/bats-core/bats-assert /bats-assert +RUN /bats-core/install.sh /usr/local diff --git a/test/Dockerfile-ubuntu b/test/Dockerfile-ubuntu index 290100d..9ee83c6 100644 --- a/test/Dockerfile-ubuntu +++ b/test/Dockerfile-ubuntu @@ -2,11 +2,33 @@ FROM ubuntu:latest # Note this image uses mawk1.3 +# Set noninteractive otherwise tzdata hangs +ENV DEBIAN_FRONTEND noninteractive + # Update and install required software RUN apt-get update --fix-missing -RUN apt-get install -y git curl dnsutils wget nginx-light +RUN apt-get install -y git curl dnsutils ldnsutils wget nginx-light jq RUN apt-get install -y vim dos2unix # for debugging -# TODO test with drill, dig, host +RUN apt-get install -y ftp vsftpd +RUN apt-get install -y openssh-server +RUN apt-get install -y locales # for idn testing + +# Set locale +RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 + +# Setup ftp +ENV VSFTPD_CONF=/etc/vsftpd.conf +ENV FTP_PASSIVE_DEFAULT=false +COPY test/test-config/vsftpd.conf /etc/vsftpd.conf +RUN adduser ftpuser +RUN echo 'ftpuser:ftpuser' | chpasswd +RUN adduser ftpuser www-data +RUN adduser root www-data +RUN chown -R www-data.www-data /var/www +RUN chmod g+w -R /var/www WORKDIR /root @@ -14,10 +36,10 @@ WORKDIR /root RUN touch /root/.rnd # BATS (Bash Automated Testings) -RUN git clone https://github.com/bats-core/bats-core.git /bats-core -RUN git clone https://github.com/jasonkarns/bats-support /bats-support -RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone --depth 1 https://github.com/bats-core/bats-support /bats-support +RUN git clone --depth 1 https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local # Run eternal loop - for testing -CMD tail -f /dev/null +CMD [ "tail", "-f", "/dev/null" ] diff --git a/test/Dockerfile-ubuntu-duckdns b/test/Dockerfile-ubuntu-duckdns index 0bdc1f8..783a151 100644 --- a/test/Dockerfile-ubuntu-duckdns +++ b/test/Dockerfile-ubuntu-duckdns @@ -2,13 +2,25 @@ FROM ubuntu:latest # Note this image uses mawk1.3 +# Set noninteractive otherwise tzdata hangs +ENV DEBIAN_FRONTEND noninteractive + +# Ensure tests in this image use the staging server ENV staging "true" -ENV DUCKDNS_TOKEN 1d616aa9-b8e4-4bb4-b312-3289de82badb +ENV dynamic_dns "duckdns" +#ENV DUCKDNS_TOKEN + # Update and install required software RUN apt-get update --fix-missing -RUN apt-get install -y git curl dnsutils wget nginx-light +RUN apt-get install -y git curl dnsutils ldnsutils wget nginx-light jq RUN apt-get install -y vim dos2unix # for debugging -# TODO test with drill, dig, host +RUN apt-get install -y locales # for idn testing + +# Set locale +RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 WORKDIR /root @@ -16,10 +28,10 @@ WORKDIR /root RUN touch /root/.rnd # BATS (Bash Automated Testings) -RUN git clone https://github.com/bats-core/bats-core.git /bats-core -RUN git clone https://github.com/jasonkarns/bats-support /bats-support -RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone --depth 1 https://github.com/bats-core/bats-support /bats-support +RUN git clone --depth 1 https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local # Run eternal loop - for testing -CMD tail -f /dev/null +CMD [ "tail", "-f", "/dev/null" ] diff --git a/test/Dockerfile-ubuntu-dynu b/test/Dockerfile-ubuntu-dynu new file mode 100644 index 0000000..a84dc08 --- /dev/null +++ b/test/Dockerfile-ubuntu-dynu @@ -0,0 +1,37 @@ +FROM ubuntu:latest + +# Note this image uses mawk1.3 + +# Set noninteractive otherwise tzdata hangs +ENV DEBIAN_FRONTEND noninteractive + +# Ensure tests in this image use the staging server +ENV staging "true" +ENV dynamic_dns "dynu" +#ENV DYNU_API_KEY + +# Update and install required software +RUN apt-get update --fix-missing +RUN apt-get install -y git curl dnsutils ldnsutils wget nginx-light jq +RUN apt-get install -y vim dos2unix # for debugging +RUN apt-get install -y locales # for idn testing + +# Set locale +RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 + +WORKDIR /root + +# Prevent "Can't load /root/.rnd into RNG" error from openssl +RUN touch /root/.rnd + +# BATS (Bash Automated Testings) +RUN git clone https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone https://github.com/bats-core/bats-support /bats-support +RUN git clone https://github.com/bats-core/bats-assert /bats-assert +RUN /bats-core/install.sh /usr/local + +# Run eternal loop - for testing +CMD tail -f /dev/null diff --git a/test/Dockerfile-ubuntu16 b/test/Dockerfile-ubuntu16 index 958bb6f..396d13d 100644 --- a/test/Dockerfile-ubuntu16 +++ b/test/Dockerfile-ubuntu16 @@ -5,21 +5,39 @@ FROM ubuntu:xenial # Update and install required software RUN apt-get update --fix-missing -RUN apt-get install -y git curl dnsutils wget nginx-light +RUN apt-get install -y git curl dnsutils ldnsutils wget nginx-light jq +RUN apt-get install -y ftp vsftpd +RUN apt-get install -y openssh-server +RUN apt-get install -y locales # for idn testing + +# Set locale +RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 WORKDIR /root -RUN mkdir /etc/nginx/pki -RUN mkdir /etc/nginx/pki/private +RUN mkdir -p /etc/nginx/pki/private COPY ./test/test-config/nginx-ubuntu-no-ssl /etc/nginx/sites-enabled/default -# Prevent "Can't load /root/.rnd into RNG" error from openssl -# RUN touch /root/.rnd +# Setup ftp +ENV VSFTPD_CONF=/etc/vsftpd.conf +ENV FTP_PASSIVE_DEFAULT=false +COPY test/test-config/vsftpd.conf /etc/vsftpd.conf +# The default init.d script seems to have an incorrect check that vsftpd has started +COPY test/test-config/vsftpd.initd /etc/init.d/vsftpd +RUN adduser ftpuser +RUN echo 'ftpuser:ftpuser' | chpasswd +RUN adduser ftpuser www-data +RUN adduser root www-data +RUN chown -R www-data.www-data /var/www +RUN chmod g+w -R /var/www # BATS (Bash Automated Testings) -RUN git clone https://github.com/bats-core/bats-core.git /bats-core -RUN git clone https://github.com/jasonkarns/bats-support /bats-support -RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone --depth 1 https://github.com/bats-core/bats-support /bats-support +RUN git clone --depth 1 https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local # Run eternal loop - for testing -CMD tail -f /dev/null +CMD [ "tail", "-f", "/dev/null" ] diff --git a/test/Dockerfile-ubuntu18 b/test/Dockerfile-ubuntu18 index ebe7607..76ce362 100644 --- a/test/Dockerfile-ubuntu18 +++ b/test/Dockerfile-ubuntu18 @@ -5,23 +5,44 @@ FROM ubuntu:bionic # Update and install required software RUN apt-get update --fix-missing -RUN apt-get install -y git curl dnsutils wget gawk nginx-light +RUN apt-get install -y git curl dnsutils ldnsutils wget gawk nginx-light jq +RUN apt-get install -y ftp vsftpd +RUN apt-get install -y openssh-server +RUN apt-get install -y locales # for idn testing + +# Set locale +RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 WORKDIR /root -RUN mkdir /etc/nginx/pki -RUN mkdir /etc/nginx/pki/private +RUN mkdir -p /etc/nginx/pki/private COPY ./test/test-config/nginx-ubuntu-no-ssl /etc/nginx/sites-enabled/default +# Setup ftp +ENV VSFTPD_CONF=/etc/vsftpd.conf +ENV FTP_PASSIVE_DEFAULT=false +COPY test/test-config/vsftpd.conf /etc/vsftpd.conf +# The default init.d script seems to have an incorrect check that vsftpd has started +COPY test/test-config/vsftpd.initd /etc/init.d/vsftpd +RUN adduser ftpuser +RUN echo 'ftpuser:ftpuser' | chpasswd +RUN adduser ftpuser www-data +RUN adduser root www-data +RUN chown -R www-data.www-data /var/www +RUN chmod g+w -R /var/www + # Prevent "Can't load /root/.rnd into RNG" error from openssl RUN touch /root/.rnd # BATS (Bash Automated Testings) -RUN git clone https://github.com/bats-core/bats-core.git /bats-core -RUN git clone https://github.com/jasonkarns/bats-support /bats-support -RUN git clone https://github.com/jasonkarns/bats-assert-1 /bats-assert +RUN git clone --depth 1 https://github.com/bats-core/bats-core.git /bats-core --branch v1.2.1 +RUN git clone --depth 1 https://github.com/bats-core/bats-support /bats-support +RUN git clone --depth 1 https://github.com/bats-core/bats-assert /bats-assert RUN /bats-core/install.sh /usr/local EXPOSE 80 443 # Run eternal loop - for testing -CMD tail -f /dev/null +CMD [ "tail", "-f", "/dev/null" ] diff --git a/test/README-Testing.md b/test/README-Testing.md index 3cd4b2c..5d67977 100644 --- a/test/README-Testing.md +++ b/test/README-Testing.md @@ -8,7 +8,24 @@ For continuous integration testing we have the following: 1. Uses `docker-compose` to start `pebble` (letsencrypt test server) and `challtestsrv` (minimal dns client for pebble) 2. Then runs the `bats` test scripts (all the files with a ".bats" extension) for each OS (alpine, centos6, debian, ubuntu) -3. Runs the `bats` test script against the staging server (using nn ubuntu docker image and duckdns.org) +3. Runs the `bats` test script against the staging server (using ubuntu docker image and duckdns.org) + +Tests can also be triggered manually from the GitHub website. + +For dynamic DNS tests, you need accounts on duckdns.org and dynu.com, and need to create 4 domain names in each account. + +For duckdns.org: +- Add DUCKDNS_TOKEN to your repository's environment secrets. The value is your account's token +- Add domains -centos7-getssl.duckdns.org, wild--centos7.duckdns.org, -ubuntu-getssl.duckdns.org, and wild--ubuntu-getssl.duckdns.org + +For dynu.com: + - Add DYNU_API_KEY to your repository's environment secrets. The value is your account's API Key. + - Add domains -centos7-getssl.freedns.org, wild--centos7.freedns.org, -ubuntu-getssl.freedns.org, and wild--ubuntu-getssl.freedns.org + +To run dynamic DNS tests outside the CI environment, you need accounts without in the domain names. Export the environment variable corresponding to the secrets (with the same values). + +For individual accounts, is your github account name. + ## To run all the tests on a single OS diff --git a/test/debug-test.sh b/test/debug-test.sh index 890366b..07e6cd6 100755 --- a/test/debug-test.sh +++ b/test/debug-test.sh @@ -10,7 +10,7 @@ if [ $# -eq 2 ]; then fi #shellcheck disable=SC1091 -source /getssl/test/test_helper.bash +source /getssl/test/test_helper.bash 3>&1 CONFIG_FILE=$1 if [ ! -e "$CONFIG_FILE" ]; then @@ -24,7 +24,7 @@ if grep -q pebble "${CONFIG_FILE}"; then export CURL_CA_BUNDLE=/root/pebble-ca-bundle.crt fi -"${CODE_DIR}/getssl" -c "$GETSSL_HOST" 3>&1 +"${CODE_DIR}/getssl" -U -c "$GETSSL_HOST" 3>&1 cp "${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" # shellcheck disable=SC2086 -"${CODE_DIR}/getssl" ${DEBUG} -f "$GETSSL_HOST" 3>&1 +"${CODE_DIR}/getssl" -U ${DEBUG} -f "$GETSSL_HOST" 3>&1 diff --git a/test/dns_add_fail b/test/dns_add_fail new file mode 100755 index 0000000..b8aa2b8 --- /dev/null +++ b/test/dns_add_fail @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +# Special test script which will always fail to update dns + +echo "dns_add_fail: This is a test script to check retry works if DNS isn't updated" +exit 0 diff --git a/test/idn-domain.md b/test/idn-domain.md new file mode 100644 index 0000000..2f25f0e --- /dev/null +++ b/test/idn-domain.md @@ -0,0 +1,22 @@ +# Convert getssl.test into IDN version using confusable letters + + + +## Unicode characters + +* ɡ 0261 LATIN SMALL LETTER SCRIPT G +* е 0435 CYRILLIC SMALL LETTER IE +* t +* ѕ 0455 CYRILLIC SMALL LETTER DZE +* ꜱ A731 LATIN LETTER SMALL CAPITAL S +* ᛁ 16C1 RUNIC LETTER ISAZ IS ISS I + +## IDN version of getssl.test + +ɡеtѕꜱᛁ.test + +## ACE version of IDN ɡеtѕꜱᛁ.test + + + +xn--t-r1a81lydm69gz81r.test diff --git a/test/restart-ftpd b/test/restart-ftpd new file mode 100755 index 0000000..6bb780c --- /dev/null +++ b/test/restart-ftpd @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +if [ -z "$1" ]; then + arg="restart" +else + arg=$1 +fi + +if [ "$GETSSL_OS" = "alpine" ]; then + killall -HUP vsftpd >&3- +elif [[ "$GETSSL_OS" == "centos"[78] || "$GETSSL_OS" == "rockylinux"* ]]; then + pgrep vsftpd | head -1 | xargs kill -HUP +elif [[ "$GETSSL_OS" == "centos6" ]]; then + service vsftpd "$arg" +else + service vsftpd restart >/dev/null >&3- +fi diff --git a/test/restart-nginx b/test/restart-nginx index e62433d..ee49af4 100755 --- a/test/restart-nginx +++ b/test/restart-nginx @@ -3,9 +3,12 @@ if [ "$GETSSL_OS" = "alpine" ]; then killall -HUP nginx >&3- sleep 5 -elif [ "$GETSSL_OS" == "centos7" ]; then +elif [[ "$GETSSL_OS" == "centos"[78] || "$GETSSL_OS" == "rockylinux"* ]]; then pgrep nginx | head -1 | xargs kill -HUP sleep 5 +elif [[ "$GETSSL_OS" == "centos6" ]]; then + service nginx restart 3>&- + # service nginx restart else service nginx restart >/dev/null >&3- fi diff --git a/test/run-test.cmd b/test/run-test.cmd index d1cf263..9d51875 100644 --- a/test/run-test.cmd +++ b/test/run-test.cmd @@ -1,43 +1,63 @@ @echo off IF %1.==. GOTO NoOS -set OS=%1 +SET OS=%1 :CheckCommand IF %2.==. GOTO NoCmd -set COMMAND=%2 %3 +SET COMMAND=%2 %3 :CheckAlias -REM check if OS *contains* duckdns -IF NOT x%OS:duckdns=%==x%OS% GOTO duckdns -set ALIAS=%OS%.getssl.test -set STAGING= +REM check if OS *contains* staging +SET GETSSL_IDN_HOST=%OS%.xn--t-r1a81lydm69gz81r.test +IF NOT x%OS:duck=%==x%OS% GOTO duckdns +IF NOT x%OS:dynu=%==x%OS% GOTO dynu +IF NOT x%OS:bash=%==x%OS% GOTO bash +SET ALIAS=%OS%.getssl.test +SET STAGING= +SET GETSSL_OS=%OS% GOTO Run :NoOS -set OS=ubuntu +SET OS=ubuntu GOTO CheckCommand :NoCmd -REM set COMMAND=/getssl/test/run-bats.sh -set COMMAND=bats /getssl/test +REM SET COMMAND=/getssl/test/run-bats.sh +SET COMMAND=bats /getssl/test --timing GOTO CheckAlias :duckdns -set ALIAS=%OS:-duckdns=%-getssl.duckdns.org -set STAGING=--env STAGING=true +SET ALIAS=%OS:-duckdns=%-getssl.duckdns.org +SET STAGING=--env STAGING=true --env dynamic_dns=duckdns --env DUCKDNS_TOKEN=1d616aa9-b8e4-4bb4-b312-3289de82badb +SET GETSSL_OS=%OS:-duckdns=% +GOTO Run + +:dynu +SET ALIAS=%OS:-dynu=%-getssl.freeddns.org +SET STAGING=--env STAGING=true --env dynamic_dns=dynu --env DYNU_API_KEY=65cXefd35XbYf36546eg5dYcZT6X52Y2 +SET GETSSL_OS=%OS:-dynu=% +GOTO Run + +:bash +SET ALIAS=%OS%.getssl.test +SET STAGING= +SET GETSSL_OS=alpine :Run -for %%I in (.) do set CurrDirName=%%~nxI +FOR %%I in (.) DO SET CurrDirName=%%~nxI -docker build --rm -f "test\Dockerfile-%OS%" -t getssl-%OS% . +docker build --pull --rm -f "test\Dockerfile-%OS%" -t getssl-%OS% . +IF %ErrorLevel% EQU 1 GOTO End @echo on docker run -it ^ --env GETSSL_HOST=%ALIAS% %STAGING% ^ - --env GETSSL_OS=%OS:-duckdns=% ^ + --env GETSSL_IDN_HOST=%GETSSL_IDN_HOST% ^ + --env GETSSL_OS=%GETSSL_OS% ^ -v %cd%:/getssl ^ --rm ^ --network %CurrDirName%_acmenet ^ --network-alias %ALIAS% ^ + --network-alias %GETSSL_IDN_HOST% ^ --network-alias a.%OS%.getssl.test ^ --network-alias b.%OS%.getssl.test ^ --network-alias c.%OS%.getssl.test ^ @@ -49,6 +69,9 @@ docker run -it ^ --network-alias i.%OS%.getssl.test ^ --network-alias j.%OS%.getssl.test ^ --network-alias k.%OS%.getssl.test ^ + --network-alias wild-%ALIAS% ^ --name getssl-%OS% ^ getssl-%OS% ^ %COMMAND% + +:End diff --git a/test/run-test.sh b/test/run-test.sh index 97842a5..e9aab38 100755 --- a/test/run-test.sh +++ b/test/run-test.sh @@ -1,36 +1,60 @@ #! /usr/bin/env bash if [ $# -eq 0 ]; then - echo "Usage: $(basename "$0") []" - echo "e.g. $(basename "$0") alpine bats /getssl/test" - exit 1 + echo "Usage: $(basename "$0") []" + echo "e.g. $(basename "$0") alpine bats /getssl/test" + exit 1 fi OS=$1 if [ $# -gt 1 ]; then - shift - COMMAND=$* + shift + COMMAND=$* else - COMMAND="bats /getssl/test" + COMMAND="bats /getssl/test --timing" fi +REPO="" +if [ -n "$GITHUB_REPOSITORY" ] ; then + REPO="$(echo "$GITHUB_REPOSITORY" | cut -d/ -f1)" + if [[ "$REPO" == "srvrco" ]] ; then + REPO="" + else + REPO="${REPO}-" + fi +fi + +ALIAS="$OS.getssl.test" +GETSSL_IDN_HOST="$OS.xn--t-r1a81lydm69gz81r.test" +STAGING="" +GETSSL_OS=$OS + if [[ "$OS" == *"duckdns"* ]]; then - ALIAS="${OS%-duckdns}-getssl.duckdns.org" - STAGING="--env STAGING=true" -else - ALIAS="$OS.getssl.test" - STAGING="" + ALIAS="${REPO}${OS%-duckdns}-getssl.duckdns.org" + STAGING="--env STAGING=true --env dynamic_dns=duckdns" + GETSSL_OS="${OS%-duckdns}" +elif [[ "$OS" == *"dynu"* ]]; then + ALIAS="${REPO}${OS%-dynu}-getssl.freeddns.org" + STAGING="--env STAGING=true --env dynamic_dns=dynu" + GETSSL_OS="${OS%-dynu}" +elif [[ "$OS" == "bash"* ]]; then + GETSSL_OS="alpine" fi docker build --rm -f "test/Dockerfile-$OS" -t "getssl-$OS" . # shellcheck disable=SC2086 docker run \ --env GETSSL_HOST=$ALIAS $STAGING \ - --env GETSSL_OS=${OS%-duckdns} \ + --env GETSSL_IDN_HOST=$GETSSL_IDN_HOST \ + --env GETSSL_OS=$GETSSL_OS \ + --env GITHUB_REPOSITORY="${GITHUB_REPOSITORY}" \ + --env DUCKDNS_TOKEN="${DUCKDNS_TOKEN}" \ + --env DYNU_API_KEY="${DYNU_API_KEY}" \ -v "$(pwd)":/getssl \ --rm \ --network ${PWD##*/}_acmenet \ --network-alias $ALIAS \ + --network-alias $GETSSL_IDN_HOST \ --network-alias "a.$OS.getssl.test" \ --network-alias "b.$OS.getssl.test" \ --network-alias "c.$OS.getssl.test" \ @@ -42,6 +66,7 @@ docker run \ --network-alias "i.$OS.getssl.test" \ --network-alias "j.$OS.getssl.test" \ --network-alias "k.$OS.getssl.test" \ + --network-alias "wild-$OS.getssl.test" \ --name "getssl-$OS" \ "getssl-$OS" \ $COMMAND diff --git a/test/test-config/alpine-supervisord.conf b/test/test-config/alpine-supervisord.conf index 9759570..bbf671e 100644 --- a/test/test-config/alpine-supervisord.conf +++ b/test/test-config/alpine-supervisord.conf @@ -12,3 +12,12 @@ stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0 autorestart=false startretries=0 + +[program:vsftpd] +command=vsftpd +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=false +startretries=0 diff --git a/test/test-config/getssl-dns01-dual-rsa-ecdsa-2-locations.cfg b/test/test-config/getssl-dns01-dual-rsa-ecdsa-2-locations.cfg new file mode 100644 index 0000000..829e73c --- /dev/null +++ b/test/test-config/getssl-dns01-dual-rsa-ecdsa-2-locations.cfg @@ -0,0 +1,37 @@ +# Test that more than one location can be specified for CERT and KEY locations and that the +# files are copied to both locations when both RSA and ECDSA certificates are created +# +CA="https://pebble:14000/dir" + +VALIDATE_VIA_DNS=true +DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" +DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" +AUTH_DNS_SERVER=10.30.50.3 + +# Speed up the test by reducing the number or retries and the wait between retries. +DNS_WAIT=2 +DNS_WAIT_COUNT=11 +DNS_EXTRA_WAIT=0 + +DUAL_RSA_ECDSA="true" +ACCOUNT_KEY_TYPE="prime256v1" +PRIVATE_KEY_ALG="prime256v1" + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="" +# SANS="a.${GETSSL_HOST}" + +# Location for all your certs, these can either be on the server (full path name) +# or using ssh /sftp as for the ACL +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key;/root/a.${GETSSL_HOST}/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="/etc/nginx/pki/domain-chain.crt;/root/a.${GETSSL_HOST}/domain-chain.crt" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-dual-certs ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" diff --git a/test/test-config/getssl-dns01-dual-rsa-ecdsa-old-nginx.cfg b/test/test-config/getssl-dns01-dual-rsa-ecdsa-old-nginx.cfg new file mode 100644 index 0000000..15b4108 --- /dev/null +++ b/test/test-config/getssl-dns01-dual-rsa-ecdsa-old-nginx.cfg @@ -0,0 +1,33 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs +# +CA="https://pebble:14000/dir" + +VALIDATE_VIA_DNS=true +DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" +DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" +AUTH_DNS_SERVER=10.30.50.3 +DNS_EXTRA_WAIT=0 + +DUAL_RSA_ECDSA="true" +ACCOUNT_KEY_TYPE="prime256v1" +PRIVATE_KEY_ALG="prime256v1" + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="" + +# Location for all your certs, these can either be on the server (full path name) +# or using ssh /sftp as for the ACL +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.ec.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.ec.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="false" diff --git a/test/test-config/getssl-dns01-dual-rsa-ecdsa.cfg b/test/test-config/getssl-dns01-dual-rsa-ecdsa.cfg index 543c201..4059dd4 100644 --- a/test/test-config/getssl-dns01-dual-rsa-ecdsa.cfg +++ b/test/test-config/getssl-dns01-dual-rsa-ecdsa.cfg @@ -8,6 +8,7 @@ VALIDATE_VIA_DNS=true DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" AUTH_DNS_SERVER=10.30.50.3 +DNS_EXTRA_WAIT=0 DUAL_RSA_ECDSA="true" ACCOUNT_KEY_TYPE="prime256v1" @@ -16,12 +17,6 @@ PRIVATE_KEY_ALG="prime256v1" # Additional domains - this could be multiple domains / subdomains in a comma separated list SANS="" -# Acme Challenge Location. The first line for the domain, the following ones for each additional domain. -ACL=('/var/www/html/.well-known/acme-challenge') - -#Set USE_SINGLE_ACL="true" to use a single ACL for all checks -USE_SINGLE_ACL="false" - # Location for all your certs, these can either be on the server (full path name) # or using ssh /sftp as for the ACL DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" @@ -31,7 +26,7 @@ DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert # The command needed to reload apache / nginx or whatever you use -RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-dual-certs ${NGINX_CONFIG} && /getssl/test/restart-nginx" # Define the server type and confirm correct certificate is installed SERVER_TYPE="https" diff --git a/test/test-config/getssl-dns01-ignore-directory-domain.cfg b/test/test-config/getssl-dns01-ignore-directory-domain.cfg new file mode 100644 index 0000000..e55fbe0 --- /dev/null +++ b/test/test-config/getssl-dns01-ignore-directory-domain.cfg @@ -0,0 +1,30 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs +# +CA="https://pebble:14000/dir" + +VALIDATE_VIA_DNS=true +DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" +DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" +AUTH_DNS_SERVER=10.30.50.3 +DNS_EXTRA_WAIT=0 + +# Ignore directory domain (i.e. the domain passed on the command line), and just use the domains in the SANS list +IGNORE_DIRECTORY_DOMAIN="true" +SANS="getssl.test,$GETSSL_HOST" + +# Location for all your certs, these can either be on the server (full path name) +# or using ssh /sftp as for the ACL +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" diff --git a/test/test-config/getssl-dns01-multiple-domains.cfg b/test/test-config/getssl-dns01-multiple-domains.cfg new file mode 100644 index 0000000..82497ad --- /dev/null +++ b/test/test-config/getssl-dns01-multiple-domains.cfg @@ -0,0 +1,29 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs +# +CA="https://pebble:14000/dir" + +VALIDATE_VIA_DNS=true +DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" +DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" +AUTH_DNS_SERVER=10.30.50.3 +DNS_EXTRA_WAIT=0 + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="getssl.test" + +# Location for all your certs, these can either be on the server (full path name) +# or using ssh /sftp as for the ACL +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" diff --git a/test/test-config/getssl-dns01-secp384.cfg b/test/test-config/getssl-dns01-secp384.cfg new file mode 100644 index 0000000..2cc360c --- /dev/null +++ b/test/test-config/getssl-dns01-secp384.cfg @@ -0,0 +1,36 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs +# +CA="https://pebble:14000/dir" + +VALIDATE_VIA_DNS=true +DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" +DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" +AUTH_DNS_SERVER=10.30.50.3 + +# Speed up the test by reducing the number or retries and the wait between retries. +DNS_WAIT=2 +DNS_WAIT_COUNT=11 +DNS_EXTRA_WAIT=0 + +ACCOUNT_KEY_TYPE="secp384r1" +PRIVATE_KEY_ALG="secp384r1" + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="" + +# Location for all your certs, these can either be on the server (full path name) +# or using ssh /sftp as for the ACL +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" diff --git a/test/test-config/getssl-dns01-spaces-and-commas-sans.cfg b/test/test-config/getssl-dns01-spaces-and-commas-sans.cfg new file mode 100644 index 0000000..204d0bf --- /dev/null +++ b/test/test-config/getssl-dns01-spaces-and-commas-sans.cfg @@ -0,0 +1,28 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs + +CA="https://pebble:14000/dir" + +VALIDATE_VIA_DNS=true +DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" +DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" +AUTH_DNS_SERVER=10.30.50.3 +DNS_EXTRA_WAIT=0 + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="a.${GETSSL_HOST}, b.${GETSSL_HOST}, c.${GETSSL_HOST}" + +# Location for all your certs, these can either be on the server (full path name) +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" diff --git a/test/test-config/getssl-dns01-spaces-sans-and-ignore-dir-domain.cfg b/test/test-config/getssl-dns01-spaces-sans-and-ignore-dir-domain.cfg new file mode 100644 index 0000000..75e7304 --- /dev/null +++ b/test/test-config/getssl-dns01-spaces-sans-and-ignore-dir-domain.cfg @@ -0,0 +1,29 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs + +CA="https://pebble:14000/dir" + +VALIDATE_VIA_DNS=true +DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" +DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" +AUTH_DNS_SERVER=10.30.50.3 +DNS_EXTRA_WAIT=0 + +# Ignore directory domain (i.e. the domain passed on the command line), and just use the domains in the SANS list +IGNORE_DIRECTORY_DOMAIN="true" +SANS="a.${GETSSL_HOST} b.${GETSSL_HOST} c.${GETSSL_HOST}" + +# Location for all your certs, these can either be on the server (full path name) +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" diff --git a/test/test-config/getssl-dns01-spaces-sans.cfg b/test/test-config/getssl-dns01-spaces-sans.cfg new file mode 100644 index 0000000..e954fa0 --- /dev/null +++ b/test/test-config/getssl-dns01-spaces-sans.cfg @@ -0,0 +1,28 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs + +CA="https://pebble:14000/dir" + +VALIDATE_VIA_DNS=true +DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" +DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" +AUTH_DNS_SERVER=10.30.50.3 +DNS_EXTRA_WAIT=0 + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="a.${GETSSL_HOST} b.${GETSSL_HOST} c.${GETSSL_HOST}" + +# Location for all your certs, these can either be on the server (full path name) +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" diff --git a/test/test-config/getssl-dns01.cfg b/test/test-config/getssl-dns01.cfg index 7e26b98..f71d19f 100644 --- a/test/test-config/getssl-dns01.cfg +++ b/test/test-config/getssl-dns01.cfg @@ -1,22 +1,40 @@ -# Uncomment and modify any variables you need -# see https://github.com/srvrco/getssl/wiki/Config-variables for details -# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs -# -CA="https://pebble:14000/dir" +# Test that the script works with dns VALIDATE_VIA_DNS=true -DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" -DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" -AUTH_DNS_SERVER=10.30.50.3 +if [ -z "$STAGING" ]; then + # Settings for challtestserv dns provider running in local docker + CA="https://pebble:14000/dir" -# Additional domains - this could be multiple domains / subdomains in a comma separated list -SANS="" + DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_challtestsrv" + DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_challtestsrv" + AUTH_DNS_SERVER=10.30.50.3 + + # Speed up the test by reducing the number or retries and the wait between retries. + DNS_WAIT=2 + DNS_WAIT_COUNT=11 + DNS_EXTRA_WAIT=0 +else + # Settings for external dns provider and staging server + CA="https://acme-staging-v02.api.letsencrypt.org/directory" -# Acme Challenge Location. The first line for the domain, the following ones for each additional domain. -ACL=('/var/www/html/.well-known/acme-challenge') + DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_${dynamic_dns}" + DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_${dynamic_dns}" + PUBLIC_DNS_SERVER="8.8.8.8 resolver1.infoserve.de" + if [[ "${dynamic_dns}" == "dynu" ]]; then + AUTH_DNS_SERVER=ns1.dynu.com + else + AUTH_DNS_SERVER=ns1.duckdns.org + fi + CHECK_ALL_AUTH_DNS="true" + CHECK_PUBLIC_DNS_SERVER="true" + DNS_EXTRA_WAIT=120 -#Set USE_SINGLE_ACL="true" to use a single ACL for all checks -USE_SINGLE_ACL="false" + DNS_WAIT_COUNT=20 + DNS_WAIT=30 + DNS_WAIT_RETRY_ADD="true" +fi +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="" # Location for all your certs, these can either be on the server (full path name) # or using ssh /sftp as for the ACL @@ -32,3 +50,7 @@ RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /get # Define the server type and confirm correct certificate is installed SERVER_TYPE="https" CHECK_REMOTE="true" + +if [[ -s "$DOMAIN_DIR/getssl_test_specific.cfg" ]]; then + . $DOMAIN_DIR/getssl_test_specific.cfg +fi diff --git a/test/test-config/getssl-etc-template.cfg b/test/test-config/getssl-etc-template.cfg new file mode 100644 index 0000000..6bfc8fd --- /dev/null +++ b/test/test-config/getssl-etc-template.cfg @@ -0,0 +1,45 @@ +# vim: filetype=sh +# +# This file is read first and is common to all domains +# +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# +# The staging server is best for testing (hence set as default) +CA="https://acme-staging-v02.api.letsencrypt.org" +# This server issues full certificates, however has rate limits +#CA="https://acme-v02.api.letsencrypt.org" + +# The agreement that must be signed with the CA, if not defined the default agreement will be used +#AGREEMENT="" + +# Set an email address associated with your account - generally set at account level rather than domain. +#ACCOUNT_EMAIL="me@example.com" +ACCOUNT_KEY_LENGTH=4096 +ACCOUNT_KEY="/etc/getssl/account.key" + +# Account key and private key types - can be rsa, prime256v1, secp384r1 or secp521r1 +#ACCOUNT_KEY_TYPE="rsa" +PRIVATE_KEY_ALG="rsa" +#REUSE_PRIVATE_KEY="true" + +# The command needed to reload apache / nginx or whatever you use +#RELOAD_CMD="" + +# The time period within which you want to allow renewal of a certificate +# this prevents hitting some of the rate limits. +# Creating a file called FORCE_RENEWAL in the domain directory allows one-off overrides +# of this setting +RENEW_ALLOW="30" + +# Define the server type. This can be https, ftp, ftpi, imap, imaps, pop3, pop3s, smtp, +# smtps_deprecated, smtps, smtp_submission, xmpp, xmpps, ldaps or a port number which +# will be checked for certificate expiry and also will be checked after +# an update to confirm correct certificate is running (if CHECK_REMOTE) is set to true +SERVER_TYPE="https" +CHECK_REMOTE="true" + +# Use the following 3 variables if you want to validate via DNS +#VALIDATE_VIA_DNS="true" +#DNS_ADD_COMMAND= +#DNS_DEL_COMMAND= diff --git a/test/test-config/getssl-http01-bad-acl.cfg b/test/test-config/getssl-http01-bad-acl.cfg new file mode 100644 index 0000000..461d3a3 --- /dev/null +++ b/test/test-config/getssl-http01-bad-acl.cfg @@ -0,0 +1,29 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs +# +CA="https://pebble:14000/dir" + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="" + +# Acme Challenge Location. +ACL= ('/var/www/html/.well-known/acme-challenge') + +#Set USE_SINGLE_ACL="true" to use a single ACL for all checks +USE_SINGLE_ACL="false" + +# Location for all your certs, these can either be on the server (full path name) +# or using ssh /sftp as for the ACL +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" diff --git a/test/test-config/getssl-http01-dual-rsa-ecdsa-2-locations-old-nginx.cfg b/test/test-config/getssl-http01-dual-rsa-ecdsa-2-locations-old-nginx.cfg new file mode 100644 index 0000000..50c3970 --- /dev/null +++ b/test/test-config/getssl-http01-dual-rsa-ecdsa-2-locations-old-nginx.cfg @@ -0,0 +1,32 @@ +# Test that more than one location can be specified for CERT and KEY locations and that the +# files are copied to both locations when both RSA and ECDSA certificates are created +# +CA="https://pebble:14000/dir" + +DUAL_RSA_ECDSA="true" +ACCOUNT_KEY_TYPE="prime256v1" +PRIVATE_KEY_ALG="prime256v1" + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="a.${GETSSL_HOST}" + +# Acme Challenge Location. +ACL=('/var/www/html/.well-known/acme-challenge') + +#Set USE_SINGLE_ACL="true" to use a single ACL for all checks +USE_SINGLE_ACL="true" + +# Location for all your certs, these can either be on the server (full path name) +# or using ssh /sftp as for the ACL +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key;/root/a.${GETSSL_HOST}/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="/etc/nginx/pki/domain-chain.crt;/root/a.${GETSSL_HOST}/domain-chain.crt" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="false" diff --git a/test/test-config/getssl-http01-dual-rsa-ecdsa-2-locations-wrong-nginx.cfg b/test/test-config/getssl-http01-dual-rsa-ecdsa-2-locations-wrong-nginx.cfg new file mode 100644 index 0000000..80533ce --- /dev/null +++ b/test/test-config/getssl-http01-dual-rsa-ecdsa-2-locations-wrong-nginx.cfg @@ -0,0 +1,32 @@ +# Test that more than one location can be specified for CERT and KEY locations and that the +# files are copied to both locations when both RSA and ECDSA certificates are created +# +CA="https://pebble:14000/dir" + +DUAL_RSA_ECDSA="true" +ACCOUNT_KEY_TYPE="prime256v1" +PRIVATE_KEY_ALG="prime256v1" + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="a.${GETSSL_HOST}" + +# Acme Challenge Location. +ACL=('/var/www/html/.well-known/acme-challenge') + +#Set USE_SINGLE_ACL="true" to use a single ACL for all checks +USE_SINGLE_ACL="true" + +# Location for all your certs, these can either be on the server (full path name) +# or using ssh /sftp as for the ACL +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key;/root/a.${GETSSL_HOST}/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="/etc/nginx/pki/domain-chain.crt;/root/a.${GETSSL_HOST}/domain-chain.crt" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" diff --git a/test/test-config/getssl-http01-dual-rsa-ecdsa-2-locations.cfg b/test/test-config/getssl-http01-dual-rsa-ecdsa-2-locations.cfg index 80533ce..96e4d4d 100644 --- a/test/test-config/getssl-http01-dual-rsa-ecdsa-2-locations.cfg +++ b/test/test-config/getssl-http01-dual-rsa-ecdsa-2-locations.cfg @@ -25,7 +25,7 @@ DOMAIN_CHAIN_LOCATION="/etc/nginx/pki/domain-chain.crt;/root/a.${GETSSL_HOST}/do DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert # The command needed to reload apache / nginx or whatever you use -RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-dual-certs ${NGINX_CONFIG} && /getssl/test/restart-nginx" # Define the server type and confirm correct certificate is installed SERVER_TYPE="https" diff --git a/test/test-config/getssl-http01-dual-rsa-ecdsa-old-nginx.cfg b/test/test-config/getssl-http01-dual-rsa-ecdsa-old-nginx.cfg new file mode 100644 index 0000000..9cf155f --- /dev/null +++ b/test/test-config/getssl-http01-dual-rsa-ecdsa-old-nginx.cfg @@ -0,0 +1,33 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs +# +CA="https://pebble:14000/dir" + +DUAL_RSA_ECDSA="true" +ACCOUNT_KEY_TYPE="prime256v1" +PRIVATE_KEY_ALG="prime256v1" + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="" + +# Acme Challenge Location. +ACL=('/var/www/html/.well-known/acme-challenge') + +#Set USE_SINGLE_ACL="true" to use a single ACL for all checks +USE_SINGLE_ACL="false" + +# Location for all your certs, these can either be on the server (full path name) +# or using ssh /sftp as for the ACL +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="false" diff --git a/test/test-config/getssl-http01-dual-rsa-ecdsa.cfg b/test/test-config/getssl-http01-dual-rsa-ecdsa.cfg index f6cfcb7..9e348fc 100644 --- a/test/test-config/getssl-http01-dual-rsa-ecdsa.cfg +++ b/test/test-config/getssl-http01-dual-rsa-ecdsa.cfg @@ -26,7 +26,7 @@ DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert # The command needed to reload apache / nginx or whatever you use -RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-dual-certs ${NGINX_CONFIG} && /getssl/test/restart-nginx" # Define the server type and confirm correct certificate is installed SERVER_TYPE="https" diff --git a/test/test-config/getssl-duckdns01.cfg b/test/test-config/getssl-http01-no-domain-storage.cfg similarity index 64% rename from test/test-config/getssl-duckdns01.cfg rename to test/test-config/getssl-http01-no-domain-storage.cfg index 517aaeb..efa5318 100644 --- a/test/test-config/getssl-duckdns01.cfg +++ b/test/test-config/getssl-http01-no-domain-storage.cfg @@ -1,21 +1,13 @@ -# Test that the script works with external dns provider and staging server +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs # -CA="https://acme-staging-v02.api.letsencrypt.org/directory" - -VALIDATE_VIA_DNS=true -DNS_ADD_COMMAND="/getssl/dns_scripts/dns_add_duckdns" -DNS_DEL_COMMAND="/getssl/dns_scripts/dns_del_duckdns" -AUTH_DNS_SERVER=1.1.1.1 -CHECK_ALL_AUTH_DNS=false -DNS_EXTRA_WAIT=60 - -ACCOUNT_KEY_TYPE="rsa" -PRIVATE_KEY_ALG="rsa" +CA="https://pebble:14000/dir" # Additional domains - this could be multiple domains / subdomains in a comma separated list SANS="" -# Acme Challenge Location. The first line for the domain, the following ones for each additional domain. +# Acme Challenge Location. ACL=('/var/www/html/.well-known/acme-challenge') #Set USE_SINGLE_ACL="true" to use a single ACL for all checks @@ -32,6 +24,8 @@ DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert # The command needed to reload apache / nginx or whatever you use RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" -# Define the server type and confirm correct certificate is installed (using a custom port) +# Define the server type and confirm correct certificate is installed SERVER_TYPE="https" CHECK_REMOTE="true" + +DOMAIN_STORAGE="/" diff --git a/test/test-config/getssl-http01-no-suffix.cfg b/test/test-config/getssl-http01-no-suffix.cfg new file mode 100644 index 0000000..a1295dd --- /dev/null +++ b/test/test-config/getssl-http01-no-suffix.cfg @@ -0,0 +1,30 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs +# +CA="https://pebble:14000/dir" +CA="https://pebble:14000" + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="" + +# Acme Challenge Location. +ACL=('/var/www/html/.well-known/acme-challenge') + +#Set USE_SINGLE_ACL="true" to use a single ACL for all checks +USE_SINGLE_ACL="false" + +# Location for all your certs, these can either be on the server (full path name) +# or using ssh /sftp as for the ACL +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" diff --git a/test/test-config/getssl-http01-spaces-and-commas-sans.cfg b/test/test-config/getssl-http01-spaces-and-commas-sans.cfg new file mode 100644 index 0000000..c4f02c7 --- /dev/null +++ b/test/test-config/getssl-http01-spaces-and-commas-sans.cfg @@ -0,0 +1,28 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs + +CA="https://pebble:14000/dir" + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="a.${GETSSL_HOST}, b.${GETSSL_HOST}, c.${GETSSL_HOST}" + +# Acme Challenge Location. +ACL=('/var/www/html/.well-known/acme-challenge') + +# Use a single ACL for all checks +USE_SINGLE_ACL="true" + +# Location for all your certs, these can either be on the server (full path name) +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" diff --git a/test/test-config/getssl-http01-spaces-sans-and-ignore-dir-domain.cfg b/test/test-config/getssl-http01-spaces-sans-and-ignore-dir-domain.cfg new file mode 100644 index 0000000..1b3cdca --- /dev/null +++ b/test/test-config/getssl-http01-spaces-sans-and-ignore-dir-domain.cfg @@ -0,0 +1,29 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs + +CA="https://pebble:14000/dir" + +# Ignore directory domain (i.e. the domain passed on the command line), and just use the domains in the SANS list +IGNORE_DIRECTORY_DOMAIN="true" +SANS="a.${GETSSL_HOST} b.${GETSSL_HOST} c.${GETSSL_HOST}" + +# Acme Challenge Location. +ACL=('/var/www/html/.well-known/acme-challenge') + +# Use a single ACL for all checks +USE_SINGLE_ACL="true" + +# Location for all your certs, these can either be on the server (full path name) +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" diff --git a/test/test-config/getssl-http01-spaces-sans.cfg b/test/test-config/getssl-http01-spaces-sans.cfg new file mode 100644 index 0000000..c93b4f7 --- /dev/null +++ b/test/test-config/getssl-http01-spaces-sans.cfg @@ -0,0 +1,28 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs + +CA="https://pebble:14000/dir" + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="a.${GETSSL_HOST} b.${GETSSL_HOST} c.${GETSSL_HOST}" + +# Acme Challenge Location. +ACL=('/var/www/html/.well-known/acme-challenge') + +# Use a single ACL for all checks +USE_SINGLE_ACL="true" + +# Location for all your certs, these can either be on the server (full path name) +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" diff --git a/test/test-config/getssl-http01-two-acl.cfg b/test/test-config/getssl-http01-two-acl.cfg new file mode 100644 index 0000000..dcf28fd --- /dev/null +++ b/test/test-config/getssl-http01-two-acl.cfg @@ -0,0 +1,29 @@ +# Uncomment and modify any variables you need +# see https://github.com/srvrco/getssl/wiki/Config-variables for details +# see https://github.com/srvrco/getssl/wiki/Example-config-files for example configs +# +CA="https://pebble:14000/dir" + +# Additional domains - this could be multiple domains / subdomains in a comma separated list +SANS="" + +# Acme Challenge Location. +ACL=('/var/www/html/.well-known/acme-challenge;/var/webroot/html/.well-known/acme-challenge') + +#Set USE_SINGLE_ACL="true" to use a single ACL for all checks +USE_SINGLE_ACL="false" + +# Location for all your certs, these can either be on the server (full path name) +# or using ssh /sftp as for the ACL +DOMAIN_CERT_LOCATION="/etc/nginx/pki/server.crt" +DOMAIN_KEY_LOCATION="/etc/nginx/pki/private/server.key" +CA_CERT_LOCATION="/etc/nginx/pki/chain.crt" +DOMAIN_CHAIN_LOCATION="" # this is the domain cert and CA cert +DOMAIN_PEM_LOCATION="" # this is the domain_key, domain cert and CA cert + +# The command needed to reload apache / nginx or whatever you use +RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /getssl/test/restart-nginx" + +# Define the server type and confirm correct certificate is installed +SERVER_TYPE="https" +CHECK_REMOTE="true" diff --git a/test/test-config/getssl-http01.cfg b/test/test-config/getssl-http01.cfg index f7d75ea..8183b16 100644 --- a/test/test-config/getssl-http01.cfg +++ b/test/test-config/getssl-http01.cfg @@ -27,3 +27,7 @@ RELOAD_CMD="cp /getssl/test/test-config/nginx-ubuntu-ssl ${NGINX_CONFIG} && /get # Define the server type and confirm correct certificate is installed SERVER_TYPE="https" CHECK_REMOTE="true" + +if [[ -s "$DOMAIN_DIR/getssl_test_specific.cfg" ]]; then + . $DOMAIN_DIR/getssl_test_specific.cfg +fi diff --git a/test/test-config/getssl-upgrade-test-pebble.cfg b/test/test-config/getssl-upgrade-test-pebble.cfg new file mode 100644 index 0000000..f5f8b3b --- /dev/null +++ b/test/test-config/getssl-upgrade-test-pebble.cfg @@ -0,0 +1,8 @@ +# +# Test that auto-upgrade to v2 doesn't change pebble url +# +CA="https://pebble:14000/dir" + + +# Acme Challenge Location. +ACL=('/var/www/html/.well-known/acme-challenge') diff --git a/test/test-config/getssl-upgrade-test-v1-prod.cfg b/test/test-config/getssl-upgrade-test-v1-prod.cfg new file mode 100644 index 0000000..41f0176 --- /dev/null +++ b/test/test-config/getssl-upgrade-test-v1-prod.cfg @@ -0,0 +1,7 @@ +# +# Test that auto-upgrade to v2 changes v1 prod to v2 prod +# +CA="https://acme-v01.api.letsencrypt.org/directory" + +# Acme Challenge Location. +ACL=('/var/www/html/.well-known/acme-challenge') diff --git a/test/test-config/getssl-upgrade-test-v1-staging.cfg b/test/test-config/getssl-upgrade-test-v1-staging.cfg new file mode 100644 index 0000000..215f246 --- /dev/null +++ b/test/test-config/getssl-upgrade-test-v1-staging.cfg @@ -0,0 +1,7 @@ +# +# Test that auto-upgrade to v2 changes v1 staging to v2 staging +# +CA="https://acme-staging.api.letsencrypt.org/directory" + +# Acme Challenge Location. +ACL=('/var/www/html/.well-known/acme-challenge') diff --git a/test/test-config/getssl-upgrade-test-v2-prod.cfg b/test/test-config/getssl-upgrade-test-v2-prod.cfg new file mode 100644 index 0000000..9b9c009 --- /dev/null +++ b/test/test-config/getssl-upgrade-test-v2-prod.cfg @@ -0,0 +1,7 @@ +# +# Test that auto-upgrade to v2 doesn't change v2 prod url +# +CA="https://acme-v02.api.letsencrypt.org/directory" + +# Acme Challenge Location. +ACL=('/var/www/html/.well-known/acme-challenge') diff --git a/test/test-config/getssl-upgrade-test-v2-staging.cfg b/test/test-config/getssl-upgrade-test-v2-staging.cfg new file mode 100644 index 0000000..98bfd17 --- /dev/null +++ b/test/test-config/getssl-upgrade-test-v2-staging.cfg @@ -0,0 +1,7 @@ +# +# Test that auto-upgrade to v2 doesn't change v2 staging url +# +CA="https://acme-staging-v02.api.letsencrypt.org/directory" + +# Acme Challenge Location. +ACL=('/var/www/html/.well-known/acme-challenge') diff --git a/test/test-config/nginx-centos7.conf b/test/test-config/nginx-centos7.conf index 2327039..f487519 100644 --- a/test/test-config/nginx-centos7.conf +++ b/test/test-config/nginx-centos7.conf @@ -1,13 +1,8 @@ -# For more information on configuration, see: -# * Official English Documentation: http://nginx.org/en/docs/ -# * Official Russian Documentation: http://nginx.org/ru/docs/ - user nginx; worker_processes auto; error_log /var/log/nginx/error.log; pid /run/nginx.pid; -# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. include /usr/share/nginx/modules/*.conf; events { @@ -30,8 +25,5 @@ http { include /etc/nginx/mime.types; default_type application/octet-stream; - # Load modular configuration files from the /etc/nginx/conf.d directory. - # See http://nginx.org/en/docs/ngx_core_module.html#include - # for more information. include /etc/nginx/conf.d/*.conf; } diff --git a/test/test-config/nginx-ubuntu-dual-certs b/test/test-config/nginx-ubuntu-dual-certs new file mode 100644 index 0000000..f6a4c80 --- /dev/null +++ b/test/test-config/nginx-ubuntu-dual-certs @@ -0,0 +1,28 @@ +server { + listen 80 default_server; + listen 5002 default_server; + listen [::]:5002 default_server; + + listen 443 ssl default_server; + listen [::]:443 ssl default_server; + + listen 5001 ssl default_server; + listen [::]:5001 ssl default_server; + + root /var/www/html; + + index index.html index.htm index.nginx-debian.html; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; + + server_name _; + ssl_certificate /etc/nginx/pki/server.crt; + ssl_certificate_key /etc/nginx/pki/private/server.key; + + ssl_certificate /etc/nginx/pki/server.ec.crt; + ssl_certificate_key /etc/nginx/pki/private/server.ec.key; + + location / { + try_files $uri $uri/ =404; + } +} diff --git a/test/test-config/nginx-ubuntu-no-ssl b/test/test-config/nginx-ubuntu-no-ssl index e7b046e..aa32d05 100644 --- a/test/test-config/nginx-ubuntu-no-ssl +++ b/test/test-config/nginx-ubuntu-no-ssl @@ -1,30 +1,21 @@ -# Default server configuration -# server { - listen 80 default_server; - listen 5002 default_server; - listen [::]:5002 default_server; + listen 80 default_server; + listen 5002 default_server; + listen [::]:5002 default_server; - # SSL configuration - # - listen 443 default_server; - listen [::]:443 default_server; + listen 443 default_server; + listen [::]:443 default_server; - listen 5001 default_server; - listen [::]:5001 default_server; + listen 5001 default_server; + listen [::]:5001 default_server; - root /var/www/html; + root /var/www/html; - # Add index.php to the list if you are using PHP - index index.html index.htm index.nginx-debian.html; + index index.html index.htm index.nginx-debian.html; - server_name _; - # ssl_certificate /etc/nginx/pki/server.crt; - # ssl_certificate_key /etc/nginx/pki/private/server.key; + server_name _; - location / { - # First attempt to serve request as file, then - # as directory, then fall back to displaying a 404. - try_files $uri $uri/ =404; - } + location / { + try_files $uri $uri/ =404; + } } diff --git a/test/test-config/nginx-ubuntu-ssl b/test/test-config/nginx-ubuntu-ssl index 9f79407..e87a497 100644 --- a/test/test-config/nginx-ubuntu-ssl +++ b/test/test-config/nginx-ubuntu-ssl @@ -1,92 +1,23 @@ -## -# You should look at the following URL's in order to grasp a solid understanding -# of Nginx configuration files in order to fully unleash the power of Nginx. -# http://wiki.nginx.org/Pitfalls -# http://wiki.nginx.org/QuickStart -# http://wiki.nginx.org/Configuration -# -# Generally, you will want to move this file somewhere, and start with a clean -# file but keep this around for reference. Or just disable in sites-enabled. -# -# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples. -## - -# Default server configuration -# server { - listen 80 default_server; - listen 5002 default_server; - listen [::]:5002 default_server; + listen 80 default_server; + listen 5002 default_server; + listen [::]:5002 default_server; - # SSL configuration - # listen 443 ssl default_server; - listen [::]:443 ssl default_server; + listen [::]:443 ssl default_server; - listen 5001 ssl default_server; - listen [::]:5001 ssl default_server; - # - # Note: You should disable gzip for SSL traffic. - # See: https://bugs.debian.org/773332 - # - # Read up on ssl_ciphers to ensure a secure configuration. - # See: https://bugs.debian.org/765782 - # - # Self signed certs generated by the ssl-cert package - # Don't use them in a production server! - # - # include snippets/snakeoil.conf; + listen 5001 ssl default_server; + listen [::]:5001 ssl default_server; - root /var/www/html; + root /var/www/html; - # Add index.php to the list if you are using PHP - index index.html index.htm index.nginx-debian.html; + index index.html index.htm index.nginx-debian.html; - server_name _; + server_name _; ssl_certificate /etc/nginx/pki/server.crt; ssl_certificate_key /etc/nginx/pki/private/server.key; - location / { - # First attempt to serve request as file, then - # as directory, then fall back to displaying a 404. - try_files $uri $uri/ =404; - } - - # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 - # - #location ~ \.php$ { - # include snippets/fastcgi-php.conf; - # - # # With php7.0-cgi alone: - # fastcgi_pass 127.0.0.1:9000; - # # With php7.0-fpm: - # fastcgi_pass unix:/run/php/php7.0-fpm.sock; - #} - - # deny access to .htaccess files, if Apache's document root - # concurs with nginx's one - # - #location ~ /\.ht { - # deny all; - #} + location / { + try_files $uri $uri/ =404; + } } - - -# Virtual Host configuration for example.com -# -# You can move that to a different file under sites-available/ and symlink that -# to sites-enabled/ to enable it. -# -#server { -# listen 80; -# listen [::]:80; -# -# server_name example.com; -# -# root /var/www/example.com; -# index index.html; -# -# location / { -# try_files $uri $uri/ =404; -# } -#} diff --git a/test/test-config/vsftpd.conf b/test/test-config/vsftpd.conf new file mode 100644 index 0000000..59ca78a --- /dev/null +++ b/test/test-config/vsftpd.conf @@ -0,0 +1,66 @@ +# Example config file /etc/vsftpd.conf (alpine) /etc/vsftpd/vsftpd.conf +# +# The default compiled in settings are fairly paranoid. This sample file +# loosens things up a bit, to make the ftp daemon more usable. +# Please see vsftpd.conf.5 for all compiled in defaults. +# +# Run standalone? vsftpd can run either from an inetd or as a standalone +# daemon started from an initscript. +listen=YES +# +# This directive enables listening on IPv6 sockets. By default, listening +# on the IPv6 "any" address (::) will accept connections from both IPv6 +# and IPv4 clients. It is not necessary to listen on *both* IPv4 and IPv6 +# sockets. If you want that (perhaps because you want to listen on specific +# addresses) then you must run two copies of vsftpd with two configuration +# files. +#listen_ipv6=NO +# +# Allow anonymous FTP? (Disabled by default). +anonymous_enable=NO +# +# Uncomment this to allow local users to log in. +local_enable=YES +# +# Uncomment this to enable any form of FTP write command. +write_enable=YES +# +# Default umask for local users is 077. You may wish to change this to 022, +# if your users expect that (022 is used by most other ftpd's) +local_umask=022 +# +# Activate directory messages - messages given to remote users when they +# go into a certain directory. +dirmessage_enable=YES +# +# If enabled, vsftpd will display directory listings with the time +# in your local time zone. The default is to display GMT. The +# times returned by the MDTM FTP command are also affected by this +# option. +use_localtime=YES +# +# Activate logging of uploads/downloads. +xferlog_enable=YES +# +# Make sure PORT transfer connections originate from port 20 (ftp-data). +connect_from_port_20=YES +# +# You may change the default value for timing out an idle session. +#idle_session_timeout=600 +# +# You may change the default value for timing out a data connection. +#data_connection_timeout=120 +# +# You may restrict local users to their home directories. See the FAQ for +# the possible risks in this before using chroot_local_user or +# chroot_list_enable below. +chroot_local_user=NO +# +# This string is the name of the PAM service vsftpd will use. +pam_service_name=vsftpd +# +# This option specifies the location of the RSA certificate to use for SSL +# encrypted connections. +rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem +rsa_private_key_file=/etc/ssl/private/ssl-cert-snakeoil.key +ssl_enable=NO diff --git a/test/test-config/vsftpd.initd b/test/test-config/vsftpd.initd new file mode 100755 index 0000000..d5b2b00 --- /dev/null +++ b/test/test-config/vsftpd.initd @@ -0,0 +1,103 @@ +#!/bin/sh + +### BEGIN INIT INFO +# Provides: vsftpd +# Required-Start: $network $remote_fs $syslog +# Required-Stop: $network $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: Very secure FTP server +# Description: Provides a lightweight, efficient FTP server written +# for security. +### END INIT INFO + +set -e + +DAEMON="/usr/sbin/vsftpd" +NAME="vsftpd" +PATH="/sbin:/bin:/usr/sbin:/usr/bin" +LOGFILE="/var/log/vsftpd.log" +CHROOT="/var/run/vsftpd/empty" + +test -x "${DAEMON}" || exit 0 + +. /lib/lsb/init-functions + +if [ ! -e "${LOGFILE}" ] +then + touch "${LOGFILE}" + chmod 640 "${LOGFILE}" + chown root:adm "${LOGFILE}" +fi + +if [ ! -d "${CHROOT}" ] +then + mkdir -p "${CHROOT}" +fi + +case "${1}" in + start) + log_daemon_msg "Starting FTP server" "${NAME}" + + if [ -e /etc/vsftpd.conf ] && ! egrep -iq "^ *listen(_ipv6)? *= *yes" /etc/vsftpd.conf + then + log_warning_msg "vsftpd disabled - listen disabled in config." + exit 0 + fi + + start-stop-daemon --start --background -m --oknodo --pidfile /var/run/vsftpd/vsftpd.pid --exec ${DAEMON} + + n=0 + while [ ${n} -le 5 ] + do + _PID="$(if [ -e /var/run/vsftpd/vsftpd.pid ]; then cat /var/run/vsftpd/vsftpd.pid; fi)" + if ! ps -C vsftpd | grep -qs "${_PID}" + then + break + fi + sleep 1 + n=$(( $n + 1 )) + done + + if ps -C vsftpd | grep -qs "${_PID}" + then + log_warning_msg "vsftpd failed - probably invalid config." + exit 1 + fi + + log_end_msg 0 + ;; + + stop) + log_daemon_msg "Stopping FTP server" "${NAME}" + + start-stop-daemon --stop --pidfile /var/run/vsftpd/vsftpd.pid --oknodo --exec ${DAEMON} + rm -f /var/run/vsftpd/vsftpd.pid + + log_end_msg 0 + ;; + + restart) + ${0} stop + ${0} start + ;; + + reload|force-reload) + log_daemon_msg "Reloading FTP server configuration" + + start-stop-daemon --stop --pidfile /var/run/vsftpd/vsftpd.pid --signal 1 --exec $DAEMON + + log_end_msg "${?}" + ;; + + status) + status_of_proc "${DAEMON}" "FTP server" + ;; + + *) + echo "Usage: ${0} {start|stop|restart|reload|status}" + exit 1 + ;; +esac + +exit 0 diff --git a/test/test_helper.bash b/test/test_helper.bash index 3ffcf51..e8d68b0 100644 --- a/test/test_helper.bash +++ b/test/test_helper.bash @@ -1,79 +1,118 @@ INSTALL_DIR=/root CODE_DIR=/getssl +check_certificates() +{ + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/chain.crt" ] + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/fullchain.crt" ] + assert [ -e "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/${GETSSL_CMD_HOST}.crt" ] +} -setup_environment() { - # One-off test setup - if [[ -d ${INSTALL_DIR}/.getssl ]]; then - rm -r ${INSTALL_DIR}/.getssl - fi - - curl --silent -X POST -d '{"host":"'"$GETSSL_HOST"'", "addresses":["'"$GETSSL_IP"'"]}' http://10.30.50.3:8055/add-a - cp ${CODE_DIR}/test/test-config/nginx-ubuntu-no-ssl "${NGINX_CONFIG}" - /getssl/test/restart-nginx +# Only nginx > 1.11.0 support dual certificates in a single configuration file +# https://unix.stackexchange.com/questions/285924/how-to-compare-a-programs-version-in-a-shell-script +check_nginx() { + requiredver="1.11.0" + currentver=$(nginx -v 2>&1 | awk -F"/" '{print $2}') + if [ "$(printf '%s\n' "$requiredver" "$currentver" | sort -V | head -n1)" = "$requiredver" ]; then + export OLD_NGINX="false" + else + echo "# INFO: Running nginx version $currentver which doesn't support dual certificates" + echo "# INFO: not checking that certificate is installed correctly" + export OLD_NGINX="true" + fi } +check_output_for_errors() { + refute_output --regexp '[Ff][Aa][Ii][Ll][Ee][Dd]' + refute_output --regexp '[^_][Ee][Rr][Rr][Oo][Rr][^:nonce]' + refute_output --regexp '[Ww][Aa][Rr][Nn][Ii][Nn][Gg]' + refute_line --partial 'command not found' +} cleanup_environment() { + if [ -z "$STAGING" ]; then curl --silent -X POST -d '{"host":"'"$GETSSL_HOST"'"}' http://10.30.50.3:8055/clear-a + fi } +create_certificate() { + # Create certificate + cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/${GETSSL_CMD_HOST}/getssl.cfg" + # shellcheck disable=SC2086 + run ${CODE_DIR}/getssl -U -d "$@" "$GETSSL_CMD_HOST" +} init_getssl() { - # Run initialisation (create account key, etc) - run ${CODE_DIR}/getssl -c "$GETSSL_HOST" - assert_success - [ -d "$INSTALL_DIR/.getssl" ] + # Run initialisation (create account key, etc) + run ${CODE_DIR}/getssl -U -d -c "$GETSSL_CMD_HOST" + assert_success + [ -d "$INSTALL_DIR/.getssl" ] } +setup_environment() { + # One-off test setup + if [[ -d ${INSTALL_DIR}/.getssl ]]; then + rm -r ${INSTALL_DIR}/.getssl + fi -create_certificate() { - # Create certificate - cp "${CODE_DIR}/test/test-config/${CONFIG_FILE}" "${INSTALL_DIR}/.getssl/${GETSSL_HOST}/getssl.cfg" - # shellcheck disable=SC2086 - run ${CODE_DIR}/getssl $1 "$GETSSL_HOST" + if [ -z "$STAGING" ]; then + # Make sure that we have cleared any previous entries, otherwise get random dns failures + curl --silent -X POST -d '{"host":"'"$GETSSL_HOST"'"}' http://10.30.50.3:8055/clear-a + curl --silent -X POST -d '{"host":"'"$GETSSL_HOST"'", "addresses":["'"$GETSSL_IP"'"]}' http://10.30.50.3:8055/add-a + fi + cp ${CODE_DIR}/test/test-config/nginx-ubuntu-no-ssl "${NGINX_CONFIG}" + /getssl/test/restart-nginx } -# start nginx in background on alpine via supervisord +# start nginx and vsftpd in background on alpine via supervisord # shellcheck disable=SC2153 # Ignore GETSSL_OS looks like typo of GETSSL_IP if [[ -f /usr/bin/supervisord && -f /etc/supervisord.conf ]]; then - if [[ ! $(pgrep supervisord) ]]; then - /usr/bin/supervisord -c /etc/supervisord.conf >&3- - fi -elif [ "$GETSSL_OS" == "centos7" ]; then - if [ -z "$(pgrep nginx)" ]; then - nginx >&3- - fi + if [[ ! $(pgrep supervisord) ]]; then + /usr/bin/supervisord -c /etc/supervisord.conf >&3- + # Give supervisord time to start + sleep 1 + fi +elif [[ "$GETSSL_OS" == "centos"[78] || "$GETSSL_OS" == "rockylinux"* ]]; then + if [ -z "$(pgrep nginx)" ]; then + nginx 3>&- + fi + if [ -z "$(pgrep vsftpd)" ] && [ "$(command -v vsftpd)" ]; then + vsftpd 3>&- + fi fi # Find NGINX configuration directory for HTTP-01 testing (need to add SSL to config) if [[ -f /etc/nginx/conf.d/default.conf ]]; then - export NGINX_CONFIG=/etc/nginx/conf.d/default.conf + export NGINX_CONFIG=/etc/nginx/conf.d/default.conf +elif [[ -f /etc/nginx/http.d/default.conf ]]; then + export NGINX_CONFIG=/etc/nginx/http.d/default.conf elif [[ -f /etc/nginx/sites-enabled/default ]]; then - export NGINX_CONFIG=/etc/nginx/sites-enabled/default + export NGINX_CONFIG=/etc/nginx/sites-enabled/default else - echo "Can't find NGINX directory" - exit 1 + echo "Can't find NGINX directory" + exit 1 fi # Find IP address if [[ -n "$(command -v ip)" ]]; then - IP=$(ip address) + GETSSL_IP=$(ip address | awk '/10.30.50/ { print $2 }' | awk -F/ '{ print $1 }') elif [[ -n "$(command -v hostname)" ]]; then - IP=$(hostname -I) + GETSSL_IP=$(hostname -I | sed -e 's/[[:space:]]*$//') else - echo "Cannot find IP address" - exit 1 + echo "Cannot find IP address" + exit 1 fi -GETSSL_IP=$(echo "$IP" | awk '/10.30.50/ { print $2 }' | awk -F/ '{ print $1 }') export GETSSL_IP -if [ ! -f ${INSTALL_DIR}/pebble.minica.pem ]; then - wget --quiet --no-clobber https://raw.githubusercontent.com/letsencrypt/pebble/master/test/certs/pebble.minica.pem 2>&1 - CERT_FILE=/etc/ssl/certs/ca-certificates.crt - if [ ! -f $CERT_FILE ]; then - CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt - fi - cat $CERT_FILE ${INSTALL_DIR}/pebble.minica.pem > ${INSTALL_DIR}/pebble-ca-bundle.crt +GETSSL_CMD_HOST=$GETSSL_HOST +export GETSSL_CMD_HOST + +if [ -z "$STAGING" ] && [ ! -f ${INSTALL_DIR}/pebble.minica.pem ]; then + wget --quiet --no-clobber https://raw.githubusercontent.com/letsencrypt/pebble/master/test/certs/pebble.minica.pem 2>&1 + CERT_FILE=/etc/ssl/certs/ca-certificates.crt + if [ ! -f $CERT_FILE ]; then + CERT_FILE=/etc/pki/tls/certs/ca-bundle.crt + fi + cat $CERT_FILE ${INSTALL_DIR}/pebble.minica.pem > ${INSTALL_DIR}/pebble-ca-bundle.crt fi diff --git a/test/u1-test-get_auth_dns-dig.bats b/test/u1-test-get_auth_dns-dig.bats new file mode 100644 index 0000000..17ead16 --- /dev/null +++ b/test/u1-test-get_auth_dns-dig.bats @@ -0,0 +1,179 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + for app in drill host nslookup + do + if [ -f /usr/bin/${app} ]; then + mv /usr/bin/${app} /usr/bin/${app}.getssl.bak + fi + done + + . /getssl/getssl --source + find_dns_utils + _USE_DEBUG=1 +} + + +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip + for app in drill host nslookup + do + if [ -f /usr/bin/${app}.getssl.bak ]; then + mv /usr/bin/${app}.getssl.bak /usr/bin/${app} + fi + done +} + + +@test "Check get_auth_dns using dig NS" { + # Test that get_auth_dns() handles scenario where NS query returns Authority section + # + # ************** EXAMPLE DIG OUTPUT ************** + # + # ;; ANSWER SECTION: + # ubuntu-getssl.duckdns.org. 60 IN A 54.89.252.137 + # + # ;; AUTHORITY SECTION: + # duckdns.org. 600 IN NS ns2.duckdns.org. + # duckdns.org. 600 IN NS ns3.duckdns.org. + # duckdns.org. 600 IN NS ns1.duckdns.org. + # + # ;; ADDITIONAL SECTION: + # ns2.duckdns.org. 600 IN A 54.191.117.119 + # ns3.duckdns.org. 600 IN A 52.26.169.94 + # ns1.duckdns.org. 600 IN A 54.187.92.222 + + # Disable CNAME check + _TEST_SKIP_CNAME_CALL=1 + + PUBLIC_DNS_SERVER=ns1.duckdns.org + CHECK_PUBLIC_DNS_SERVER=false + CHECK_ALL_AUTH_DNS=false + + run get_auth_dns ubuntu-getssl.duckdns.org + + # Assert that we've found the primary_ns server + assert_output --regexp 'set primary_ns = ns[1-9]+\.duckdns\.org' + # Assert that we had to use dig NS + assert_line --regexp 'Using dig.* NS' + + # Check all Authoritive DNS servers are returned if requested + CHECK_ALL_AUTH_DNS=true + run get_auth_dns ubuntu-getssl.duckdns.org + assert_output --regexp 'set primary_ns = (ns[1-9]+\.duckdns\.org )+' +} + + +@test "Check get_auth_dns using dig SOA" { + # Test that get_auth_dns() handles scenario where SOA query returns Authority section + # + # ************** EXAMPLE DIG OUTPUT ************** + # + # ;; AUTHORITY SECTION: + # duckdns.org. 600 IN SOA ns3.duckdns.org. hostmaster.duckdns.org. 2019170803 6000 120 2419200 600 + + # DuckDNS server returns nothing for SOA, so use public dns instead + PUBLIC_DNS_SERVER=1.0.0.1 + CHECK_PUBLIC_DNS_SERVER=false + CHECK_ALL_AUTH_DNS=false + + run get_auth_dns ubuntu-getssl.duckdns.org + + # Assert that we've found the primary_ns server + assert_output --regexp 'set primary_ns = ns[1-9]+\.duckdns\.org' + + # Assert that we had to use dig NS + assert_line --regexp 'Using dig.* SOA' + refute_line --regexp 'Using dig.* NS' + + # Check all Authoritive DNS servers are returned if requested + CHECK_ALL_AUTH_DNS=true + run get_auth_dns ubuntu-getssl.duckdns.org + assert_output --regexp 'set primary_ns = (ns[1-9]+\.duckdns\.org )+' + + # Check that we also check the public DNS server if requested + CHECK_PUBLIC_DNS_SERVER=true + run get_auth_dns ubuntu-getssl.duckdns.org + assert_output --regexp 'set primary_ns = (ns[1-9]+\.duckdns\.org )+1\.0\.0\.1' +} + + +@test "Check get_auth_dns using dig CNAME (public dns)" { + # Test that get_auth_dns() handles scenario where CNAME query returns just a CNAME record + # + # ************** EXAMPLE DIG OUTPUT ************** + # + # ;; ANSWER SECTION: + # www.duckdns.org. 600 IN CNAME DuckDNSAppELB-570522007.us-west-2.elb.amazonaws.com. + + # Disable SOA check + _TEST_SKIP_SOA_CALL=1 + + PUBLIC_DNS_SERVER=1.0.0.1 + CHECK_PUBLIC_DNS_SERVER=false + CHECK_ALL_AUTH_DNS=false + + run get_auth_dns www.duckdns.org + + # Assert that we've found the primary_ns server + assert_output --regexp 'set primary_ns = ns.*\.awsdns.*\.net' + + # Assert that we found a CNAME and use dig NS + assert_line --regexp 'Using dig.* CNAME' + assert_line --regexp 'Using dig.* NS' + + # Check all Authoritive DNS servers are returned if requested + CHECK_ALL_AUTH_DNS=true + run get_auth_dns www.duckdns.org + assert_output --regexp 'set primary_ns = ns.*\.awsdns.*\.net' + + # Check that we also check the public DNS server if requested + CHECK_PUBLIC_DNS_SERVER=true + run get_auth_dns www.duckdns.org + assert_output --regexp 'set primary_ns = ns.*\.awsdns.*\.net 1\.0\.0\.1' +} + + +@test "Check get_auth_dns using dig CNAME (duckdns)" { + # Test that get_auth_dns() handles scenario where CNAME query returns authority section containing NS records + # + # ************** EXAMPLE DIG OUTPUT ************** + # + # ;; ANSWER SECTION: + # www.duckdns.org. 600 IN CNAME DuckDNSAppELB-570522007.us-west-2.elb.amazonaws.com. + # + # ;; AUTHORITY SECTION: + # duckdns.org. 600 IN NS ns1.duckdns.org. + # duckdns.org. 600 IN NS ns2.duckdns.org. + # duckdns.org. 600 IN NS ns3.duckdns.org. + # + # ;; ADDITIONAL SECTION: + # ns1.duckdns.org. 600 IN A 54.187.92.222 + # ns2.duckdns.org. 600 IN A 54.191.117.119 + # ns3.duckdns.org. 600 IN A 52.26.169.94 + + PUBLIC_DNS_SERVER=ns1.duckdns.org + CHECK_PUBLIC_DNS_SERVER=false + CHECK_ALL_AUTH_DNS=false + + run get_auth_dns www.duckdns.org + + # Assert that we've found the primary_ns server + assert_output --regexp 'set primary_ns = ns[1-9]+\.duckdns\.org' + + # Assert that we found a CNAME but didn't use dig NS + assert_line --regexp 'Using dig.* CNAME' + refute_line --regexp 'Using dig.* NS' + + # Check all Authoritive DNS servers are returned if requested + CHECK_ALL_AUTH_DNS=true + run get_auth_dns www.duckdns.org + assert_output --regexp 'set primary_ns = (ns[1-9]+\.duckdns\.org )+' +} diff --git a/test/u2-test-get_auth_dns-drill.bats b/test/u2-test-get_auth_dns-drill.bats new file mode 100644 index 0000000..4884a60 --- /dev/null +++ b/test/u2-test-get_auth_dns-drill.bats @@ -0,0 +1,203 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + for app in dig host nslookup + do + if [ -f /usr/bin/${app} ]; then + mv /usr/bin/${app} /usr/bin/${app}.getssl.bak + fi + done + + . /getssl/getssl --source + find_dns_utils + _USE_DEBUG=1 +} + + +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip + for app in dig host nslookup + do + if [ -f /usr/bin/${app}.getssl.bak ]; then + mv /usr/bin/${app}.getssl.bak /usr/bin/${app} + fi + done +} + + +@test "Check get_auth_dns using drill NS" { + if [ ! -f /usr/bin/drill ]; then + # Can't find drill package for centos8 + skip "Drill not installed on this system" + fi + + # Test that get_auth_dns() handles scenario where NS query returns Authority section + # + # ************** EXAMPLE DRILL OUTPUT ************** + # + # ;; ANSWER SECTION: + # ubuntu-getssl.duckdns.org. 60 IN A 54.89.252.137 + # + # ;; AUTHORITY SECTION: + # duckdns.org. 600 IN NS ns2.duckdns.org. + # duckdns.org. 600 IN NS ns3.duckdns.org. + # duckdns.org. 600 IN NS ns1.duckdns.org. + # + # ;; ADDITIONAL SECTION: + # ns2.duckdns.org. 600 IN A 54.191.117.119 + # ns3.duckdns.org. 600 IN A 52.26.169.94 + # ns1.duckdns.org. 600 IN A 54.187.92.222 + + # Disable SOA and CNAME check + _TEST_SKIP_CNAME_CALL=1 + _TEST_SKIP_SOA_CALL=1 + + PUBLIC_DNS_SERVER=ns1.duckdns.org + CHECK_PUBLIC_DNS_SERVER=false + CHECK_ALL_AUTH_DNS=false + + run get_auth_dns ubuntu-getssl.duckdns.org + + # Assert that we've found the primary_ns server + assert_output --regexp 'set primary_ns = ns[1-9]+\.duckdns\.org' + # Assert that we had to use drill NS + assert_line --regexp 'Using drill.* NS' + + # Check all Authoritive DNS servers are returned if requested + CHECK_ALL_AUTH_DNS=true + run get_auth_dns ubuntu-getssl.duckdns.org + assert_output --regexp 'set primary_ns = (ns[1-9]+\.duckdns\.org )+' +} + + +@test "Check get_auth_dns using drill SOA" { + if [ ! -f /usr/bin/drill ]; then + # Can't find drill package for centos8 + skip "Drill not installed on this system" + fi + + # Test that get_auth_dns() handles scenario where SOA query returns Authority section + # + # ************** EXAMPLE DRILL OUTPUT ************** + # + # ;; AUTHORITY SECTION: + # duckdns.org. 600 IN SOA ns3.duckdns.org. hostmaster.duckdns.org. 2019170803 6000 120 2419200 600 + + # DuckDNS server returns nothing for SOA, so use public dns instead + PUBLIC_DNS_SERVER=1.0.0.1 + CHECK_PUBLIC_DNS_SERVER=false + CHECK_ALL_AUTH_DNS=false + + run get_auth_dns ubuntu-getssl.duckdns.org + + # Assert that we've found the primary_ns server + assert_output --regexp 'set primary_ns = ns[1-9]+\.duckdns\.org' + + # Assert that we had to use drill NS + assert_line --regexp 'Using drill.* SOA' + refute_line --regexp 'Using drill.* NS' + + # Check all Authoritive DNS servers are returned if requested + CHECK_ALL_AUTH_DNS=true + run get_auth_dns ubuntu-getssl.duckdns.org + assert_output --regexp 'set primary_ns = (ns[1-9]+\.duckdns\.org )+' + + # Check that we also check the public DNS server if requested + CHECK_PUBLIC_DNS_SERVER=true + run get_auth_dns ubuntu-getssl.duckdns.org + assert_output --regexp 'set primary_ns = (ns[1-9]+\.duckdns\.org )+1\.0\.0\.1' +} + + +@test "Check get_auth_dns using drill CNAME (public dns)" { + if [ ! -f /usr/bin/drill ]; then + # Can't find drill package for centos8 + skip "Drill not installed on this system" + fi + + # Test that get_auth_dns() handles scenario where CNAME query returns just a CNAME record + # + # ************** EXAMPLE drill OUTPUT ************** + # + # ;; ANSWER SECTION: + # www.duckdns.org. 600 IN CNAME DuckDNSAppELB-570522007.us-west-2.elb.amazonaws.com. + + # Disable SOA check + _TEST_SKIP_SOA_CALL=1 + + PUBLIC_DNS_SERVER=1.0.0.1 + CHECK_PUBLIC_DNS_SERVER=false + CHECK_ALL_AUTH_DNS=false + + run get_auth_dns www.duckdns.org + + # Assert that we've found the primary_ns server + assert_output --regexp 'set primary_ns = ns.*\.awsdns.*\.net' + + # Assert that we found a CNAME and use drill NS + assert_line --regexp 'Using drill.* CNAME' + assert_line --regexp 'Using drill.* NS' + + # Check all Authoritive DNS servers are returned if requested + CHECK_ALL_AUTH_DNS=true + run get_auth_dns www.duckdns.org + assert_output --regexp 'set primary_ns = ns.*\.awsdns.*\.net' + + # Check that we also check the public DNS server if requested + CHECK_PUBLIC_DNS_SERVER=true + run get_auth_dns www.duckdns.org + assert_output --regexp 'set primary_ns = ns.*\.awsdns.*\.net 1\.0\.0\.1' +} + + +@test "Check get_auth_dns using drill CNAME (duckdns)" { + if [ ! -f /usr/bin/drill ]; then + # Can't find drill package for centos8 + skip "Drill not installed on this system" + fi + + # Test that get_auth_dns() handles scenario where CNAME query returns authority section containing NS records + # + # ************** EXAMPLE drill OUTPUT ************** + # + # ;; ANSWER SECTION: + # www.duckdns.org. 600 IN CNAME DuckDNSAppELB-570522007.us-west-2.elb.amazonaws.com. + # + # ;; AUTHORITY SECTION: + # duckdns.org. 600 IN NS ns1.duckdns.org. + # duckdns.org. 600 IN NS ns2.duckdns.org. + # duckdns.org. 600 IN NS ns3.duckdns.org. + # + # ;; ADDITIONAL SECTION: + # ns1.duckdns.org. 600 IN A 54.187.92.222 + # ns2.duckdns.org. 600 IN A 54.191.117.119 + # ns3.duckdns.org. 600 IN A 52.26.169.94 + + # Disable SOA check + _TEST_SKIP_SOA_CALL=1 + + PUBLIC_DNS_SERVER=ns1.duckdns.org + CHECK_PUBLIC_DNS_SERVER=false + CHECK_ALL_AUTH_DNS=false + + run get_auth_dns www.duckdns.org + + # Assert that we've found the primary_ns server + assert_output --regexp 'set primary_ns = ns[1-9]+\.duckdns\.org' + + # Assert that we found a CNAME but didn't use drill NS + assert_line --regexp 'Using drill.* CNAME' + refute_line --regexp 'Using drill.* NS' + + # Check all Authoritive DNS servers are returned if requested + CHECK_ALL_AUTH_DNS=true + run get_auth_dns www.duckdns.org + assert_output --regexp 'set primary_ns = (ns[1-9]+\.duckdns\.org )+' +} diff --git a/test/u3-mktemp-template.bats b/test/u3-mktemp-template.bats new file mode 100644 index 0000000..c42c0b5 --- /dev/null +++ b/test/u3-mktemp-template.bats @@ -0,0 +1,18 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" +} +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + +@test "Check mktemp -t getssl.XXXXXX works on all platforms" { + run mktemp -t getssl.XXXXXX + assert_success +} diff --git a/test/u4-create-csr-and-ifs.bats b/test/u4-create-csr-and-ifs.bats new file mode 100644 index 0000000..4e5280e --- /dev/null +++ b/test/u4-create-csr-and-ifs.bats @@ -0,0 +1,58 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + . /getssl/getssl --source + find_dns_utils + _USE_DEBUG=1 +} + + +@test "Check create_csr works for multiple domains" { + # Create a key + csr_key=$(mktemp -t getssl.key.XXXXXX) || error_exit "mktemp failed" + csr_file=$(mktemp -t getssl.csr.XXXXXX) || error_exit "mktemp failed" + SANS="a.getssl.test,b.getssl.test" + SANLIST="subjectAltName=DNS:${SANS//[, ]/,DNS:}" + create_key "$ACCOUNT_KEY_TYPE" "$csr_key" "$ACCOUNT_KEY_LENGTH" + + # Create an initial csr + run create_csr $csr_file $csr_key + assert_success + + # Check that calling create_csr with the same SANSLIST doesn't re-create the csr + run create_csr $csr_file $csr_key + assert_success + refute_line --partial "does not have the same domains" + + # Check that calling create_csr with a different SANSLIST does re-create the csr + SANS="a.getssl.test,b.getssl.test,c.getssl.test" + SANLIST="subjectAltName=DNS:${SANS//[, ]/,DNS:}" + run create_csr $csr_file $csr_key + assert_success + assert_line --partial "does not contain" + + # Check that calling create_csr with the same SANSLIST, but in a different order does not re-create the csr + SANS="c.getssl.test,a.getssl.test,b.getssl.test" + SANLIST="subjectAltName=DNS:${SANS//[, ]/,DNS:}" + run create_csr $csr_file $csr_key + assert_success + refute_line --partial "does not contain" + + # Check that removing a domain from the SANSLIST causes the csr to be re-created + SANS="c.getssl.test,a.getssl.test" + SANLIST="subjectAltName=DNS:${SANS//[, ]/,DNS:}" + run create_csr $csr_file $csr_key + assert_success + assert_line --partial "does not have the same domains as the config" +} diff --git a/test/u5-test-get_auth_dns-no-root-servers.bats b/test/u5-test-get_auth_dns-no-root-servers.bats new file mode 100644 index 0000000..5fd743f --- /dev/null +++ b/test/u5-test-get_auth_dns-no-root-servers.bats @@ -0,0 +1,97 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + + +# This is run for every test +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + cp /etc/resolv.conf /etc/resolv.conf.getssl + cat <<- EOF > /etc/resolv.conf +nameserver 8.8.8.8 +options ndots:0 +EOF + + for app in drill host nslookup + do + if [ -f /usr/bin/${app} ]; then + mv /usr/bin/${app} /usr/bin/${app}.getssl.bak + fi + done + + . /getssl/getssl --source + find_dns_utils + _USE_DEBUG=1 +} + + +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip + cat /etc/resolv.conf.getssl > /etc/resolv.conf + for app in drill host nslookup + do + if [ -f /usr/bin/${app}.getssl.bak ]; then + mv /usr/bin/${app}.getssl.bak /usr/bin/${app} + fi + done +} + + +@test "Check get_auth_dns doesn't include root servers (dig NS)" { + # Test that get_auth_dns() handles scenario where NS query returns root servers + # Issue #617 + # + # Log output was: + # Verifying example.com + # checking DNS at h.root-servers.net for example.com. Attempt 1/100 gave wrong result, waiting 10 secs before checking again + # ... (retried until max attempts then failed) + + # Disable SOA and CNAME check + _TEST_SKIP_CNAME_CALL=1 + _TEST_SKIP_SOA_CALL=1 + + PUBLIC_DNS_SERVER= + CHECK_PUBLIC_DNS_SERVER=false + CHECK_ALL_AUTH_DNS=true + + run get_auth_dns example.com + + # Assert that we've found the primary_ns server + assert_output --regexp 'set primary_ns = ' + # Assert that we had to use dig NS + assert_line --regexp 'Using dig.* NS' + + # Check we didn't include any root servers + refute_line --partial 'root-servers.net' +} + + +@test "Check get_auth_dns doesn't include root servers (dig SOA)" { + # Test that get_auth_dns() handles scenario where NS query returns root servers + # Issue #617 + # + # Log output was: + # Verifying example.com + # checking DNS at h.root-servers.net for example.com. Attempt 1/100 gave wrong result, waiting 10 secs before checking again + # ... (retried until max attempts then failed) + + # Disable CNAME check, ensure SOA check is enabled + _TEST_SKIP_CNAME_CALL=1 + _TEST_SKIP_SOA_CALL=0 + + PUBLIC_DNS_SERVER= + CHECK_PUBLIC_DNS_SERVER=false + CHECK_ALL_AUTH_DNS=true + + run get_auth_dns example.com + + # Assert that we've found the primary_ns server + assert_output --regexp 'set primary_ns = ' + # Assert that we had to use dig SOA + assert_line --regexp 'Using dig.* SOA' + + # Check we didn't include any root servers + refute_line --partial 'root-servers.net' +} diff --git a/test/u6-test-combined-directory.bats b/test/u6-test-combined-directory.bats new file mode 100644 index 0000000..0bafe13 --- /dev/null +++ b/test/u6-test-combined-directory.bats @@ -0,0 +1,32 @@ +#! /usr/bin/env bats + +load '/bats-support/load.bash' +load '/bats-assert/load.bash' +load '/getssl/test/test_helper.bash' + +# CA with a unified directory (both ACME V1 and V2 at the same URI) +CA="https://api.test4.buypass.no/acme" + +# This is run for every test +setup() { + [ ! -f $BATS_RUN_TMPDIR/failed.skip ] || skip "skipping tests after first failure" + + . /getssl/getssl --source + + requires curl + _NOMETER="--silent" + + _USE_DEBUG=1 +} + + +teardown() { + [ -n "$BATS_TEST_COMPLETED" ] || touch $BATS_RUN_TMPDIR/failed.skip +} + + +@test "Check that API V2 is selected in a unified ACME directory." { + obtain_ca_resource_locations + + [ "$API" -eq 2 ] +}