lando / lando

A development tool for all your projects that is fast, easy, powerful and liberating
https://lando.dev
GNU General Public License v3.0
4.09k stars 545 forks source link

How to setup curl cert on lando start #2677

Closed morious closed 1 year ago

morious commented 3 years ago

Hi All,

When running lando start i am getting the following error -

curl: (60) SSL certificate problem: self signed certificate in certificate chain More details here: https://curl.haxx.se/docs/sslcerts.html

curl performs SSL certificate verification by default, using a "bundle" of Certificate Authority (CA) public keys (CA certs). If the default bundle file isn't adequate, you can specify an alternate file using the --cacert option. If this HTTPS server uses a certificate signed by a CA represented in the bundle, the certificate verification probably failed due to a problem with the certificate (it might be expired, or the name might not match the domain name in the URL). If you'd like to turn off curl's verification of the certificate, use the -k (or --insecure) option. ERROR ==> the -k (or --insecure) option.

Is there a way to control the curl env that lando is using?

Thanks!

wesleymusgrove commented 3 years ago

I'm getting this error too and I'm running behind a ZScalar proxy.

When I use a custom php.ini in order to provide the correct CA file during the build... it fails when it's trying to do a composer install and also fails later when I lando ssh into my appserver and run a curl command.

It says this about the CAfile: none

curl -v https://packagist.org
...
* successfully set certificate verify locations:
*   CAfile: none
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CONNECT phase completed!
* CONNECT phase completed!
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS alert, unknown CA (560):
* SSL certificate problem: unable to get local issuer certificate
* Closing connection 0
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

When I change my curl command to provide the CA file explicitly for the ZScalar Root CA, it works:

curl -v https://packagist.org --cacert /app/.lando/config/ZscalerRootCertificate-2048-SHA256.crt
...
* successfully set certificate verify locations:
*   CAfile: /app/.lando/config/ZscalerRootCertificate-2048-SHA256.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* CONNECT phase completed!
* CONNECT phase completed!
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: CN=packagist.org
*  start date: Feb  8 02:05:59 2021 GMT
*  expire date: Feb 22 02:05:59 2021 GMT
*  subjectAltName: host "packagist.org" matched cert's "packagist.org"
*  issuer: C=US; ST=California; O=Zscaler Inc.; OU=Zscaler Inc.; CN=Zscaler Intermediate Root CA (zscalerthree.net) (t)
*  SSL certificate verify ok.
> GET / HTTP/1.1
> Host: packagist.org
> User-Agent: curl/7.64.0
> Accept: */*
>
< HTTP/1.1 200 OK

These are some links on using your own custom php.ini so you can specify the path to where your CA file lives. https://stackoverflow.com/questions/24611640/curl-60-ssl-certificate-problem-unable-to-get-local-issuer-certificate https://www.drupal.org/node/2481341 https://stackoverflow.com/questions/62116666/composer-installer-error-on-windows-openssl-failed-with-a-certificate-verify-f

From my .lando.yml file:

services:
  appserver:
    config:
      php: .lando/config/php.ini

In that php.ini file I'm specifying the CA file for my ZScalar proxy like this:

[curl]
curl.cainfo = "/app/.lando/config/ZscalerRootCertificate-2048-SHA256.crt"

[openssl]
openssl.cafile = "/app/.lando/config/ZscalerRootCertificate-2048-SHA256.crt"

When I lando ssh into the appserver and run php -i, it says that curl is configured with the correct CA file that I specified in the php.ini file.

Directive => Local Value => Master Value
curl.cainfo => /app/.lando/config/ZscalerRootCertificate-2048-SHA256.crt => /app/.lando/config/ZscalerRootCertificate-2048-SHA256.crt

Directive => Local Value => Master Value
openssl.cafile => /app/.lando/config/ZscalerRootCertificate-2048-SHA256.crt => /app/.lando/config/ZscalerRootCertificate-2048-SHA256.crt
openssl.capath => no value => no value

image

image

Why is curl not using the curl.cainfo or the openssl.cafile provided in the php.ini?

It works when I specify the CA file using the --cacert option, but I need it to be configured and working without specifying that option so my build steps work correctly.

Am I missing something?

Thanks!

mikedoubintchik commented 3 years ago

@wesleymusgrove What you suggested with the custom php.ini file actually worked for the composer part that was using openssl. Thank you!

curl wasn't working still.

wesleymusgrove commented 3 years ago

Hey @mikedoubintchik! Check out an example .lando.yml buried in the middle of this comment: https://github.com/lando/lando/issues/2888#issuecomment-800702119. That issue is where I figured out a bunch of other similar issues.

I found that I actually had to copy over my .curlrc file in a build step like this:

    build:
      - cp /app/.lando/config/.curlrc ~/.curlrc

Contents of my .curlrc:

cacert /app/.lando/config/ca-all.pem
proxy http://host.docker.internal:9000

For easier reference, here's the whole .lando.yml:

name: hello-lando
recipe: lamp
config:
  webroot: .
  composer_version: false
  ssl: true

keys:
  - id_rsa

proxy:
  appserver:
    - hello.lando

services:
  appserver:
    ## Use a custom php.ini to instruct curl and openssl to use the ca-all.pem, which includes the Zscaler cert.
    config:
      php: .lando/config/php.ini
    build_as_root:
      - cp /app/.lando/config/ZscalerRootCertificate2048SHA256.pem /usr/local/share/ca-certificates/ZscalerRootCertificate2048SHA256.crt && chmod 644 /usr/local/share/ca-certificates/ZscalerRootCertificate2048SHA256.crt && update-ca-certificates
      - cp /app/.lando/config/ca-all.pem /usr/local/share/ca-certificates/ca-all.pem && chmod 644 /usr/local/share/ca-certificates/ca-all.pem && update-ca-certificates
      - ln -sf /app/.lando/config/ZscalerRootCertificate2048SHA256.pem /etc/ssl/certs/ZscalerRootCertificate2048SHA256.crt
      - ln -sf /app/.lando/config/ca-all.pem /etc/ssl/certs/ca-all.pem
    build:
      - cp /app/.lando/config/.curlrc ~/.curlrc
      ## Install composer myself instead of letting Lando do it because I need to be able to set the default stream context inline for php.
      - /app/.lando/scripts/install-composer.sh 2.0.9
    overrides:
      environment:
        HTTP_PROXY: "http://host.docker.internal:9000"
        HTTPS_PROXY: "http://host.docker.internal:9000"

A couple other things you may or may not need to do is install the CA certificates in a build_as_root step. That was necessary for me being behind a corporate VPN and Zscaler proxy. I actually had to install BOTH the ca-all.pem and the ZscalerRootCertificate2048SHA256.pem individually. By the way, ca-all.pem is just this file https://curl.se/ca/cacert.pem with my ZScaler cert appended to the end of it in PEM format.

Also if you're having trouble getting Lando's composer helper to install correctly, I found out that I actually had to prevent their /helpers/install-composer.sh script from running and provide my own so that I could specify the correct PHP stream_context_create to use my corporate proxy:

Contents of my custom install-composer.sh script:

#!/bin/sh

set -e

VERSION="$1"

# NOTE: we should have better protections here
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php', stream_context_create(['http' => ['proxy' => 'tcp://host.docker.internal:9000']]));"
php -r "if (hash_file('sha384', 'composer-setup.php') === '756890a4488ce9024fc62c56153228907f1545c228516cbf63f885e036d37e9a59d27d63f46af1d4d07ee0f76181c7d3') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"

# Allow for a few convenience install methods
if [ "$VERSION" = '1-latest' ]; then
  php composer-setup.php --install-dir=/usr/local/bin --filename=composer --1
elif [ "$VERSION" = '1' ]; then
  php composer-setup.php --install-dir=/usr/local/bin --filename=composer --1
elif [ "$VERSION" = '2-latest' ]; then
  php composer-setup.php --install-dir=/usr/local/bin --filename=composer --2
elif [ "$VERSION" = '2' ]; then
  php composer-setup.php --install-dir=/usr/local/bin --filename=composer --2
elif [ "$VERSION" = 'preview' ]; then
  php composer-setup.php --install-dir=/usr/local/bin --filename=composer --preview
elif [ "$VERSION" = 'snapshot' ]; then
  php composer-setup.php --install-dir=/usr/local/bin --filename=composer --snapshot
else
  php composer-setup.php --install-dir=/usr/local/bin --filename=composer --version="$VERSION"
fi

# Remove the setup script
php -r "unlink('composer-setup.php');"

# Check if anything is installed globally
if [ -f /var/www/.composer/composer.json ]; then
  # If this is version 2 then let's make sure hirak/prestissimo is removed
  if composer --version 2>/dev/null | grep -E "Composer (version )?2." > /dev/null; then
    composer global remove hirak/prestissimo
  fi
fi
mikedoubintchik commented 3 years ago

@wesleymusgrove Thanks for the follow up!

Unfortunately, the drush curl command that's shipped with the drupal8 recipe is still failing for me even with the .curlrc file and the ca-certs copied

wesleymusgrove commented 3 years ago

@mikedoubintchik post your .lando.yml, the command you're running, and the error and I'll see if I can help!

mikedoubintchik commented 3 years ago

@wesleymusgrove Appreciate you taking a look at it!

I'm also behind a corporate VPN with some crazy self-signed certs

cacerts.pem contain the cert that work when using the --cacert flag with the curl command

I've confirmed the certs get updated after lando start by ssh'ing into the container

I don't see a .curlrc file, but assuming that's because that happens during the build step, it's not the www-data user that has it

The error I see is the exact same one that started this github issue. I think it happens when the Lando recipe installs Drush with a curl command.

.lando.yml:

name: inkwell
recipe: drupal8
config:
  webroot: ./web
  database: postgres:9.6.14
  xdebug: true
  config:
    php: ./.lando/config/php.ini
env_file:
  - inkwell.env
services:
  appserver:
    # before services boot up
    build:
      - cp ./.lando/config/.curlrc ~/.curlrc
    build_as_root:
      - cp /app/certificates/cacerts.pem /usr/local/share/ca-certificates/cacerts.pem && chmod 644 /usr/local/share/ca-certificates/cacerts.pem && update-ca-certificates
      - ln -sf /app/certificates/cacerts.pem /etc/ssl/certs/cacerts.pem

php.ini:

[curl]
curl.cainfo = "/app/certificates/cacerts.pem"

[openssl]
openssl.cafile = "/app/certificates/cacerts.pem"

.curlrc:

cacert /app/certificates/cacerts.pem
wesleymusgrove commented 3 years ago

@mikedoubintchik A couple things you might want to try:

Have you already appended any additional corporate self signed certs to the end of cacerts.pem before you add it to your lando in the build_as_root step?

If so (and I know this sounds crazy), but in my experience I'm almost 100% confident that this is your issue. I had to also install that special corporate self-signed cert INDIVIDUALLY. That's why I'm also doing this for the ZScaler cert in addition to ca-all.pem, which ALSO has the ZScaler cert appended to the end of it anyway. It actually makes a difference and blows up trying to download drupal-console for me via curl!!! I hope this solves your issue. Try adding your self-signed corporate cert individually in this way.

build_as_root:
      - cp /app/.lando/config/ZscalerRootCertificate2048SHA256.pem /usr/local/share/ca-certificates/ZscalerRootCertificate2048SHA256.crt && chmod 644 /usr/local/share/ca-certificates/ZscalerRootCertificate2048SHA256.crt
      - cp /app/.lando/config/ca-all.pem /usr/local/share/ca-certificates/ca-all.pem && chmod 644 /usr/local/share/ca-certificates/ca-all.pem
      - update-ca-certificates

If not, you might take a look at your C:\Users[username].lando\logs\inkwell.log and see if you can find the relevant lines that get logged out for each build step. Here's an excerpt from my other more complicated lando app (not the hello world example). You'll notice that on mine when it installs drush, it's actually just adding it as a required dependency to the composer.json with composer global require drush/drush:^9, which will get installed later on when I run composer install as a build step INSTEAD of using curl to download it. I'm guessing when yours tries to download drush it looks more like mine does when it actually tries to use curl to download drupal-console like this curl https://drupalconsole.com/installer?

docker-compose.exe up
docker-compose.exe exec node /helpers/user-perms.sh
docker-compose.exe up
docker-compose.exe exec appserver /helpers/user-perms.sh
docker-compose.exe exec appserver /bin/sh docker-php-ext-enable xdebug
docker-compose.exe exec appserver /bin/sh cp /app/.lando/config/ZscalerRootCertificate2048SHA256.pem /usr/local/share/ca-certificates/ZscalerRootCertificate2048SHA256.crt && chmod 644 /usr/local/share/ca-certificates/ZscalerRootCertificate2048SHA256.crt
docker-compose.exe exec appserver /bin/sh cp /app/.lando/config/ca-all.pem /usr/local/share/ca-certificates/ca-all.pem && chmod 644 /usr/local/share/ca-certificates/ca-all.pem
docker-compose.exe exec appserver /bin/sh update-ca-certificates
docker-compose.exe exec appserver /bin/sh curl https://drupalconsole.com/installer -L -o /tmp/drupal.phar && chmod +x /tmp/drupal.phar && mv /tmp/drupal.phar /usr/local/bin/drupal && true
docker-compose.exe exec appserver /bin/sh composer global require drush/drush:^9
docker-compose.exe exec appserver /bin/sh cp /app/.lando/config/.curlrc ~/.curlrc
docker-compose.exe exec appserver /bin/sh cp /app/.lando/config/config ~/.ssh/config
docker-compose.exe exec appserver /bin/sh mkdir -p ~/.drush/site-aliases
docker-compose.exe exec appserver /bin/sh ln -sf /app/drush/sites/www.site.yml ~/.drush/site-aliases/www.site.yml
docker-compose.exe exec appserver /bin/sh /app/.lando/scripts/install-composer.sh 2-latest
docker-compose.exe exec appserver /bin/sh composer --version
docker-compose.exe exec appserver /bin/sh composer install

You might try adding drush: ^9 to your .lando.yml config instead to see if it will add it the composer way. Actually it looks like you have a nested config under your top level config for the php.ini stuff. I'm not sure if that's the correct syntax. I had to put mine under services > appserver > config > php, like this:

name: inkwell
recipe: drupal8
config:
  webroot: ./web
  database: postgres:9.6.14
  drush: ^9    <--------------------------ADDED THIS
  xdebug: true
env_file:
  - inkwell.env
services:
  appserver:
    config:        <-----------------------------MOVED THIS HERE
      php: ./.lando/config/php.ini   <-----------------------------MOVED THIS HERE
    # before services boot up
    build:
      - cp ./.lando/config/.curlrc ~/.curlrc
      - composer install       <---------------------ARE YOU USING COMPOSER?  MIGHT WANT TO INSTALL HERE?
    build_as_root:
      - cp /app/certificates/cacerts.pem /usr/local/share/ca-certificates/cacerts.pem && chmod 644 /usr/local/share/ca-certificates/cacerts.pem && update-ca-certificates
      - ln -sf /app/certificates/cacerts.pem /etc/ssl/certs/cacerts.pem

Let me know how that goes! I wrestled with this for a several weeks!

mikedoubintchik commented 3 years ago

I think the real trick was whatever you did to get composer to install drush instead of having lando do it globally: https://docs.lando.dev/config/drupal8.html#using-a-site-local-drush

For some reason, lando is not respecting the drush in my composer file. I've tried to specify the tooling to point at the local drush, but still not working.

wesleymusgrove commented 3 years ago

@mikedoubintchik Hmm try a tooling command like this. Your web root may be different than mine.

tooling:
  drush:
    service: appserver
    cmd: /app/vendor/drush/drush/drush --root=/app/docroot
mikedoubintchik commented 3 years ago

In the end, I realized that drush was being installed by something other than the lando drupal recipe. Most likely it's being installed by composer. I realized this because the global drush install was still failing and I removed my manual drush installation in the "run as root" post build phase. Drush continued to work.

So for now, even though global drush through lando recipe is still failing and showing the curl ssl errors, I'm dropping trying to fix this.

I still needed to add the self-signed certs to the php.ini which made composer work on our corporate VPN. Without those certs, composer wouldn't run.

Thanks for all the help @wesleymusgrove

wesleymusgrove commented 3 years ago

No problem @mikedoubintchik!

I don't know if you tried this or not, but for me the golden ticket to get the global drush (and drupal console) to download successfully was to also install the individual CA cert specific to your company's proxy IN ADDITION to installing the https://curl.se/ca/cacert.pem with my ZScaler cert appended to the end of it in PEM format.

So I ended up needing to install two certs like this:

build_as_root:
      - cp /app/.lando/config/ZscalerRootCertificate2048SHA256.pem /usr/local/share/ca-certificates/ZscalerRootCertificate2048SHA256.crt && chmod 644 /usr/local/share/ca-certificates/ZscalerRootCertificate2048SHA256.crt
      - cp /app/.lando/config/ca-all.pem /usr/local/share/ca-certificates/ca-all.pem && chmod 644 /usr/local/share/ca-certificates/ca-all.pem
      - update-ca-certificates
mikedoubintchik commented 3 years ago

We actually switched to using docker compose which has been much faster for us and more flexibility to resolve all of these issues caused by the corporate VPN. Highly recommend it!

Thanks again for your help :)

fmitchell commented 3 years ago

I fixed this issue by:

  1. Downloading the cacert.pem file from https://curl.se/docs/caextract.html
  2. Putting the file in a config directory, along with my php.ini
  3. My php.ini has these two lines on it
    curl.cainfo = "/app/config/cacert.pem"
    openssl.cafile = "/app/config/cacert.pem"
  4. Added this to my .lando.yml file
    services:
    appserver:
    config:
      php: config/php.ini
  5. Confirmed it's loading by running lando php --info | grep curl and seeing the path to cert
  6. Rebuild with lando rebuild
stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions and please check out this if you are wondering why we auto close issues.

stale[bot] commented 1 year ago

We haven't heard anything here for about a year so we are automatically closing this issue to keep things tidy. If this is in error then please post in this thread and request the issue be reopened!