diff --git a/Dockerfile b/Dockerfile
index fc83a034..a7f8c878 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,6 +4,7 @@ RUN apt-get install -y make software-properties-common curl
RUN curl -sL https://deb.nodesource.com/setup_7.x | bash -
RUN apt-get update
RUN apt-get install -y npm libldap2-dev libsasl2-dev libldap2-dev libssl-dev
+RUN pip install pip==20.0.2
RUN pip install -U setuptools
RUN pip install coveralls bandit
WORKDIR /app
diff --git a/docs/production/index.rst b/docs/production/index.rst
index b91ed6bd..67e97dae 100644
--- a/docs/production/index.rst
+++ b/docs/production/index.rst
@@ -451,3 +451,53 @@ LetsEncrypt flow to function. However, Lemur will attempt to automatically deter
possible. To enable this functionality, periodically (or through Cron/Celery) run `lemur dns_providers get_all_zones`.
This command will traverse all DNS providers, determine which zones they control, and upload this list of zones to
Lemur's database (in the dns_providers table). Alternatively, you can manually input this data.
+
+
+LetsEncrypt: pinning to cross-signed ICA
+----------------------------------------
+
+Let's Encrypt has been using a `cross-signed `_ intermediate CA by DST Root CA X3,
+which is included in many older devices' TrustStore.
+
+
+Let's Encrypt is `transitioning `_ to use
+the intermediate CA issued by their own root (ISRG X1) starting from September 29th 2020.
+This is in preparation of concluding the initial bootstrapping of their CA, by having it cross-signed by an older CA.
+
+
+Lemur can temporarily pin to the cross-signed intermediate CA (same public/private key pair as the ICA signed by ISRG X1).
+This will prolong support for incompatible devices.
+
+The following must be added to the config file to activate the pinning (the pinning will be removed by September 2021)::
+
+ # remove or update after Mar 17 16:40:46 2021 GMT
+ IDENTRUST_CROSS_SIGNED_LE_ICA_EXPIRATION_DATE = "17/03/21"
+ IDENTRUST_CROSS_SIGNED_LE_ICA = """
+ -----BEGIN CERTIFICATE-----
+ MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
+ MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
+ DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
+ SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
+ GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
+ AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
+ q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
+ SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
+ Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
+ a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
+ /PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
+ AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
+ CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
+ bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
+ c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
+ VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
+ ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
+ MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
+ Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
+ AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
+ uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
+ wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
+ X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
+ PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
+ KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
+ -----END CERTIFICATE-----
+ """
diff --git a/lemur/plugins/lemur_acme/plugin.py b/lemur/plugins/lemur_acme/plugin.py
index 3fc1df61..16d61a0f 100644
--- a/lemur/plugins/lemur_acme/plugin.py
+++ b/lemur/plugins/lemur_acme/plugin.py
@@ -205,9 +205,15 @@ class AcmeHandler(object):
OpenSSL.crypto.FILETYPE_PEM, orderr.fullchain_pem
),
).decode()
- pem_certificate_chain = orderr.fullchain_pem[
- len(pem_certificate) : # noqa
- ].lstrip()
+
+ if current_app.config.get("IDENTRUST_CROSS_SIGNED_LE_ICA", False) \
+ and datetime.datetime.now() < datetime.datetime.strptime(
+ current_app.config.get("IDENTRUST_CROSS_SIGNED_LE_ICA_EXPIRATION_DATE", "17/03/21"), '%d/%m/%y'):
+ pem_certificate_chain = current_app.config.get("IDENTRUST_CROSS_SIGNED_LE_ICA")
+ else:
+ pem_certificate_chain = orderr.fullchain_pem[
+ len(pem_certificate) : # noqa
+ ].lstrip()
current_app.logger.debug(
"{0} {1}".format(type(pem_certificate), type(pem_certificate_chain))
diff --git a/lemur/plugins/lemur_acme/tests/test_acme.py b/lemur/plugins/lemur_acme/tests/test_acme.py
index 94949a74..8320a2de 100644
--- a/lemur/plugins/lemur_acme/tests/test_acme.py
+++ b/lemur/plugins/lemur_acme/tests/test_acme.py
@@ -156,6 +156,7 @@ class TestAcme(unittest.TestCase):
mock_acme.fetch_chain = Mock(return_value="mock_chain")
mock_crypto.dump_certificate = Mock(return_value=b"chain")
mock_order = Mock()
+ mock_current_app.config = {}
self.acme.request_certificate(mock_acme, [], mock_order)
def test_setup_acme_client_fail(self):
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 6c8df1e4..2299848e 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -11,7 +11,7 @@ cffi==1.14.0 # via cryptography
cfgv==3.1.0 # via pre-commit
chardet==3.0.4 # via requests
colorama==0.4.3 # via twine
-cryptography==2.9.2 # via secretstorage
+cryptography==3.0 # via secretstorage
distlib==0.3.0 # via virtualenv
docutils==0.16 # via readme-renderer
filelock==3.0.12 # via virtualenv
diff --git a/requirements-docs.txt b/requirements-docs.txt
index c37fbb20..3d1ed54c 100644
--- a/requirements-docs.txt
+++ b/requirements-docs.txt
@@ -10,23 +10,23 @@ alembic-autogenerate-enums==0.0.2 # via -r requirements.txt
alembic==1.4.2 # via -r requirements.txt, flask-migrate
amqp==2.5.2 # via -r requirements.txt, kombu
aniso8601==8.0.0 # via -r requirements.txt, flask-restful
-arrow==0.15.7 # via -r requirements.txt
+arrow==0.15.8 # via -r requirements.txt
asyncpool==1.0 # via -r requirements.txt
babel==2.8.0 # via sphinx
bcrypt==3.1.7 # via -r requirements.txt, flask-bcrypt, paramiko
beautifulsoup4==4.9.1 # via -r requirements.txt, cloudflare
billiard==3.6.3.0 # via -r requirements.txt, celery
blinker==1.4 # via -r requirements.txt, flask-mail, flask-principal, raven
-boto3==1.14.23 # via -r requirements.txt
-botocore==1.17.23 # via -r requirements.txt, boto3, s3transfer
+boto3==1.14.33 # via -r requirements.txt
+botocore==1.17.33 # via -r requirements.txt, boto3, s3transfer
celery[redis]==4.4.2 # via -r requirements.txt
certifi==2020.6.20 # via -r requirements.txt, requests
certsrv==2.1.1 # via -r requirements.txt
cffi==1.14.0 # via -r requirements.txt, bcrypt, cryptography, pynacl
chardet==3.0.4 # via -r requirements.txt, requests
click==7.1.1 # via -r requirements.txt, flask
-cloudflare==2.8.6 # via -r requirements.txt
-cryptography==2.9.2 # via -r requirements.txt, acme, josepy, paramiko, pyopenssl, requests
+cloudflare==2.8.8 # via -r requirements.txt
+cryptography==3.0 # via -r requirements.txt, acme, josepy, paramiko, pyopenssl, requests
dnspython3==1.15.0 # via -r requirements.txt
dnspython==1.15.0 # via -r requirements.txt, dnspython3
docutils==0.15.2 # via -r requirements.txt, botocore, sphinx
@@ -43,7 +43,7 @@ flask-sqlalchemy==2.4.4 # via -r requirements.txt, flask-migrate
flask==1.1.2 # via -r requirements.txt, flask-bcrypt, flask-cors, flask-mail, flask-migrate, flask-principal, flask-restful, flask-script, flask-sqlalchemy, raven
future==0.18.2 # via -r requirements.txt
gunicorn==20.0.4 # via -r requirements.txt
-hvac==0.10.4 # via -r requirements.txt
+hvac==0.10.5 # via -r requirements.txt
idna==2.9 # via -r requirements.txt, requests
imagesize==1.2.0 # via sphinx
inflection==0.5.0 # via -r requirements.txt
diff --git a/requirements-tests.txt b/requirements-tests.txt
index a3f037e9..7fd13f76 100644
--- a/requirements-tests.txt
+++ b/requirements-tests.txt
@@ -10,16 +10,16 @@ aws-sam-translator==1.22.0 # via cfn-lint
aws-xray-sdk==2.5.0 # via moto
bandit==1.6.2 # via -r requirements-tests.in
black==19.10b0 # via -r requirements-tests.in
-boto3==1.14.23 # via aws-sam-translator, moto
+boto3==1.14.33 # via aws-sam-translator, moto
boto==2.49.0 # via moto
-botocore==1.17.23 # via aws-xray-sdk, boto3, moto, s3transfer
+botocore==1.17.33 # via aws-xray-sdk, boto3, moto, s3transfer
certifi==2020.6.20 # via requests
cffi==1.14.0 # via cryptography
cfn-lint==0.29.5 # via moto
chardet==3.0.4 # via requests
click==7.1.1 # via black, flask
-coverage==5.2 # via -r requirements-tests.in
-cryptography==2.9.2 # via moto, sshpubkeys
+coverage==5.2.1 # via -r requirements-tests.in
+cryptography==3.0 # via moto, sshpubkeys
decorator==4.4.2 # via networkx
docker==4.2.0 # via moto
docutils==0.15.2 # via botocore
@@ -34,6 +34,7 @@ gitdb==4.0.4 # via gitpython
gitpython==3.1.1 # via bandit
idna==2.8 # via moto, requests
importlib-metadata==1.6.0 # via jsonpickle
+iniconfig==1.0.1 # via pytest
itsdangerous==1.1.0 # via flask
jinja2==2.11.2 # via flask, moto
jmespath==0.9.5 # via boto3, botocore
@@ -52,7 +53,7 @@ packaging==20.3 # via pytest
pathspec==0.8.0 # via black
pbr==5.4.5 # via stevedore
pluggy==0.13.1 # via pytest
-py==1.8.1 # via pytest
+py==1.9.0 # via pytest
pyasn1==0.4.8 # via python-jose, rsa
pycparser==2.20 # via cffi
pyflakes==2.2.0 # via -r requirements-tests.in
@@ -60,7 +61,7 @@ pyparsing==2.4.7 # via packaging
pyrsistent==0.16.0 # via jsonschema
pytest-flask==1.0.0 # via -r requirements-tests.in
pytest-mock==3.2.0 # via -r requirements-tests.in
-pytest==5.4.3 # via -r requirements-tests.in, pytest-flask, pytest-mock
+pytest==6.0.1 # via -r requirements-tests.in, pytest-flask, pytest-mock
python-dateutil==2.8.1 # via botocore, faker, freezegun, moto
python-jose==3.1.0 # via moto
pytz==2019.3 # via moto
@@ -78,10 +79,9 @@ sortedcontainers==2.1.0 # via fakeredis
sshpubkeys==3.1.0 # via moto
stevedore==1.32.0 # via bandit
text-unidecode==1.3 # via faker
-toml==0.10.0 # via black
+toml==0.10.0 # via black, pytest
typed-ast==1.4.1 # via black
urllib3==1.25.8 # via botocore, requests
-wcwidth==0.1.9 # via pytest
websocket-client==0.57.0 # via docker
werkzeug==1.0.1 # via flask, moto, pytest-flask
wrapt==1.12.1 # via aws-xray-sdk
diff --git a/requirements.txt b/requirements.txt
index a42fe3f6..46723b0d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -9,22 +9,22 @@ alembic-autogenerate-enums==0.0.2 # via -r requirements.in
alembic==1.4.2 # via flask-migrate
amqp==2.5.2 # via kombu
aniso8601==8.0.0 # via flask-restful
-arrow==0.15.7 # via -r requirements.in
+arrow==0.15.8 # via -r requirements.in
asyncpool==1.0 # via -r requirements.in
bcrypt==3.1.7 # via flask-bcrypt, paramiko
beautifulsoup4==4.9.1 # via cloudflare
billiard==3.6.3.0 # via celery
blinker==1.4 # via flask-mail, flask-principal, raven
-boto3==1.14.23 # via -r requirements.in
-botocore==1.17.23 # via -r requirements.in, boto3, s3transfer
+boto3==1.14.33 # via -r requirements.in
+botocore==1.17.33 # via -r requirements.in, boto3, s3transfer
celery[redis]==4.4.2 # via -r requirements.in
certifi==2020.6.20 # via -r requirements.in, requests
certsrv==2.1.1 # via -r requirements.in
cffi==1.14.0 # via bcrypt, cryptography, pynacl
chardet==3.0.4 # via requests
click==7.1.1 # via flask
-cloudflare==2.8.6 # via -r requirements.in
-cryptography==2.9.2 # via -r requirements.in, acme, josepy, paramiko, pyopenssl, requests
+cloudflare==2.8.8 # via -r requirements.in
+cryptography==3.0 # via -r requirements.in, acme, josepy, paramiko, pyopenssl, requests
dnspython3==1.15.0 # via -r requirements.in
dnspython==1.15.0 # via dnspython3
docutils==0.15.2 # via botocore
@@ -41,7 +41,7 @@ flask-sqlalchemy==2.4.4 # via -r requirements.in, flask-migrate
flask==1.1.2 # via -r requirements.in, flask-bcrypt, flask-cors, flask-mail, flask-migrate, flask-principal, flask-restful, flask-script, flask-sqlalchemy, raven
future==0.18.2 # via -r requirements.in
gunicorn==20.0.4 # via -r requirements.in
-hvac==0.10.4 # via -r requirements.in
+hvac==0.10.5 # via -r requirements.in
idna==2.9 # via requests
inflection==0.5.0 # via -r requirements.in
itsdangerous==1.1.0 # via flask