Certbot: No module named pip.__main__; ‘pip’ is a package and cannot be directly executed

De un tiempo a esta parte he tenido sistemas de certificados Let’s Encrypt que han dejado de renovar el certificado automáticamente, al ejecutar certbot-auto obtengo el siguiente mensaje de error:

/opt/eff.org/certbot/venv/bin/python: No module named pip.__main__; 'pip' is a package and cannot be directly executed
Traceback (most recent call last):
  File "/tmp/tmp.p48KR72ZCs/pipstrap.py", line 177, in <module>
    sys.exit(main())
  File "/tmp/tmp.p48KR72ZCs/pipstrap.py", line 149, in main
    pip_version = StrictVersion(check_output([python, '-m', 'pip', '--version'])
  File "/usr/lib/python2.7/subprocess.py", line 544, in check_output
    raise CalledProcessError(retcode, cmd, output=output)
subprocess.CalledProcessError: Command '['/opt/eff.org/certbot/venv/bin/python', '-m', 'pip', '--version']' returned non-zero exit status 1

Opcion 1

La solución que he encontrado es actualizar a mano certbot-auto e indicarle que corra sin tratar de actualizarse. Estos serían los comandos:

rm ./certbot-auto*
wget https://raw.githubusercontent.com/certbot/certbot/75499277be6699fd5a9b884837546391950a3ec9/certbot-auto
chmod +x ./certbot-auto
./certbot-auto --no-self-upgrade

La referencia a este procedimiento la he conseguido de este post en foro de la comunidad Let’s Encrypt.

Opcion 2

Es posible parchar certbot-auto directamente con estos cambios

pip_version = StrictVersion(check_output([python, '-m', 'pip', '--version'])

cambiar por:

pip_version = StrictVersion(check_output(['pip', '--version'])

y

command = [python, ‘-m’, ‘pip’, ‘install’, ‘–no-index’, ‘–no-deps’, ‘-U’]

cambiar por:

command = ['pip', 'install', '--no-index', '--no-deps', '-U']

Enviar correo SMTP+SSL por telnet

No es precisamente telnet ya que telnet no implementa SSL, pero me parece un buen título para explicar a que se refiere este artículo.

Siguiendo con el artículo de «Correo POP3+SSL por telnet», ahora explico cómo hacer telnet (a Gmail) para enviar correo.

Obtener usuario/clave

El usuario y clave son pasados codificados en base64, por lo que antes de empezar conviene tener el string de validación, con alguno de estos dos comandos:

$ perl -MMIME::Base64 -e 'print encode_base64("\000pilasguru\@gmail.com\000password")'
AHBpbGFzZ3VydUBnbWFpbC5jb20AcGFzc3dvcmQ=
$ printf "\0pilasguru@gmail.com\0password" | openssl enc -a
AHBpbGFzZ3VydUBnbWFpbC5jb20AcGFzc3dvcmQ=

como se aprecia, ambos devuelven el mismo string.

Conexión

El comando openssl con la opción s_client será el encargado de establecer la conexión:

s_client This implements a generic SSL/TLS client which can establish a transparent connection to a remote server speaking SSL/TLS. It’s intended for testing purposes only and provides only rudimentary interface functionality but internally uses mostly all functionality of the OpenSSL ssl library.

de la siguiente forma:

$ openssl s_client -host smtp.gmail.com -port 587 -starttls smtp -crlf

CONNECTED(00000003)
depth=2 /C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
verify error:num=20:unable to get local issuer certificate
verify return:0
---
Certificate chain
 0 s:/C=US/ST=California/L=Mountain View/O=Google Inc/CN=smtp.gmail.com
   i:/C=US/O=Google Inc/CN=Google Internet Authority G2
 1 s:/C=US/O=Google Inc/CN=Google Internet Authority G2
   i:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
 2 s:/C=US/O=GeoTrust Inc./CN=GeoTrust Global CA
   i:/C=US/O=Equifax/OU=Equifax Secure Certificate Authority
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIEdjCCA16gAwIBAgIIOuQOXm7sFPMwDQYJKoZIhvcNAQEFBQAwSTELMAkGA1UE
BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl
cm5ldCBBdXRob3JpdHkgRzIwHhcNMTMwOTEwMDc1NDQ3WhcNMTQwOTEwMDc1NDQ3
WjBoMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwN
TW91bnRhaW4gVmlldzETMBEGA1UECgwKR29vZ2xlIEluYzEXMBUGA1UEAwwOc210
cC5nbWFpbC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpMKDa
E9bW18yuVMulny5K5YLwf7ebEpINUVPZXvp7cO6vNjl+MCHjhbB2Rkg7QVJE8eNS
V0Hpq3vOuz+RQ2rPKfaeM3MFBZJ+tKscC39XmlVtmyBW5AVWy5dlO7718MQCN/L5
kpYSY6RinFrf5pIlf5XSGRCo3WYndguPP1A+X4gsDKjMaWhCP5KfczLHGTY+4T+d
31lDSah8CbFeMvKav0SFnyRYM36YAvAk2HH1/64Tolbx9tMAW6e6q8dU1U6W5u6+
Bt7WjW1iYwwfML+ZorKR9p+V070nDDN42ZE8HVZw+hOl9eMl48L/eX0eKbSGZ
....
J/3lYLI71meuut7O7G+BcFlXVphs5XSy65LkziTXikR+MRERjCKhv3AwP0oGB2+q
APMUqxtH6K6hmFE5ELtYjS4rKLbH08s8gy65y/EiaBaWKBlKG6s+r22uyxu2xmgo
LFf94N1gVJXuaZXlCgVwThCtbekh8wxjHtcVw2HCZfzQemEr7oshVOX2
-----END CERTIFICATE-----
subject=/C=US/ST=California/L=Mountain View/O=Google Inc/CN=smtp.gmail.com
issuer=/C=US/O=Google Inc/CN=Google Internet Authority G2
---
No client certificate CA names sent
---
SSL handshake has read 3474 bytes and written 470 bytes
---
New, TLSv1/SSLv3, Cipher is RC4-SHA
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1
    Cipher    : RC4-SHA
    Session-ID: 65AAADD952AE24108001D17D0FF6C5403E5CE85040F61346A1C80C8E753E394F
    Session-ID-ctx:
    Master-Key: F8B3C5E3C2C0435ED53542A36CBB8ECA635255FBAEF73F1ADDB7BC512657E9C9A9B7E7EB567227856648A4D54C63CCA7
    Key-Arg   : None
    Start Time: 1400863594
    Timeout   : 300 (sec)
    Verify return code: 20 (unable to get local issuer certificate)
---
250 CHUNKING

A partir de aquí podemos establecer el diálogo SMTP con el servidor de gmail enviando el comando ehlo:

ehlo
250-mx.google.com at your service, [64.90.52.109]
250-SIZE 35882577
250-8BITMIME
250-AUTH LOGIN PLAIN XOAUTH XOAUTH2 PLAIN-CLIENTTOKEN
250-ENHANCEDSTATUSCODES
250-PIPELINING
250 CHUNKING

Luego validando nuestro usuario con el string que otuvimos previamente:

AUTH PLAIN AHBpbGFzZ3VydUBnbWFpbC5jb20AcGFzc3dvcmQ=
235 2.7.0 Accepted

A partir de aqui, se pueden enviar los comandos SMTP estandar: MAIL FROM, MAIL TO, DATA y punto para terminar el mensaje.

Verificando certificados SSL desde la línea de comandos

Recientemente he necesitado revisar el vencimiento de certificados SSL mediante un script que me permitiera conocer el estado de los mismos en muchos servidores.

Una primer opción era ejecutar el comando openssl para verificar cada certificado:

$ openssl x509 -in certificado.pem -noout -enddate
notAfter=Oct 24 23:59:59 2014 GMT

y automatizarlo mediante conexiones ssh, pero el problema es que los certificados no tienen todos el mismo nombre, ni están instalados en el mismo directorio, pues algunos fueron instalados dependiendo del servicio.

Así que la opción fue utilizar openssl para abrir conexiones como cliente y verificar qué vencimiento tiene el certificado del servicio correspondiente:

$ echo | openssl s_client -connect servidor.com:443 2>/dev/null | openssl x509 -noout -dates
notBefore=Oct 24 00:00:00 2013 GMT
notAfter=Oct 24 23:59:59 2014 GMT

El echo inicial envía un enter al comando openssl para que el cliente no conectado y de esta forma con un listado de nombres de servidor y los puertos, se puede revisar toda una infraestructura de servidores y servicios.

Algunas argumentos que pueden ser pasados a openssl para obtener datos de certificados X509:

  • –dates fechas del período de validez del certificado
  • –enddate fecha de vencimiento
  • –subject titular del certificado y CN
  • –issuer autoridad de certificación
  • –fingerprint huella dactilar del certificado
  • –text toda la información del certificado

Estos comandos pueden ser utilizados en conjunto para obtener más de una información en la misma consulta:

$ echo | openssl s_client -connect servidor.com:443 2>/dev/null | openssl x509 -noout -dates -issuer -fingerprint
notBefore=Oct 24 00:00:00 2013 GMT
notAfter=Oct 24 23:59:59 2014 GMT
issuer= /C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=EssentialSSL CA
SHA1 Fingerprint=6F:24:52:8F:10:E0:6D:AB:C9:BB:1D:52:65:F6:A8:47:89:BE:A3:5F

y quitando el -noout se obtiene el certificado:

$ echo | openssl s_client -connect servidor.com:443 2>/dev/null | openssl x509
-----BEGIN CERTIFICATE-----
MIIFIjCCBAqgAwIBAgIQMK1wptvY36IzHyscziufKTANBgkqhkiG9w0BAQUFADBy
MQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYD
...
nZpAVhzwHIS5wzMMHc5XRFvv3Ohnsco1CAA76HBBrMjmC6OYFXVDX+LgXjnTvuS8
lpg865wT5NUeWtWoRCOQydIHXqatpEyysbBZ/QbUA9CjjWViowRfYub2jZJ3v1k4
SsEYTJTsv3duHmn5Zn+dhINdVgVEJA==
-----END CERTIFICATE-----

y, por supuesto, para la consulta de un solo certificado, a veces es útil alguna herramienta web como esta de SSLShopper

Con curl


$ curl -s --insecure -v https://servidor.com \
| awk 'BEGIN { cert=0 } /^\* Server certificate:/ \
{ cert=1 } /^\*/ { if (cert) print }'

* Rebuilt URL to: https://servidor.com/
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 13.249.87.93...
* TCP_NODELAY set
* Connected to servidor.com (x.x.x.x) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
* CAfile: /etc/ssl/certs/ca-certificates.crt
CApath: /etc/ssl/certs
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
} [5 bytes data]
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
} [512 bytes data]
* TLSv1.2 (IN), TLS handshake, Server hello (2):
{ [76 bytes data]

...

* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use http/1.1
* Server certificate:
* subject: CN=servidor.com
* start date: Aug 16 00:00:00 2019 GMT
* expire date: Sep 16 12:00:00 2020 GMT
* issuer: C=US; O=Amazon; OU=Server CA 1B; CN=Amazon
* SSL certificate verify ok.
} [5 bytes data]
> GET / HTTP/1.1
> Host: servidor.com
> User-Agent: curl/7.52.1
> Accept: */*
>