Weblogs Código

Blog Bitix

Configurar un servidor web virtual en Nginx y Apache

agosto 09, 2020 12:00

Nginx

Apache HTTPD

La función básica de un servidor web es devolver al navegador los recursos estáticos de un sitio web como son las páginas HTML, hojas de estilo CSS, imágenes o archivos de JavaScript. Pero también pueden hacer otras funciones muy útiles como son la de proxy entre en navegador y la aplicación de backend que genera el contenido dinámico, terminación de protocolo de seguridad con la que el navegador se comunica con el servidor web usando el protocolo seguro HTTPS pero el servidor web se comunica con la aplicación backend usando el protocolo HTTP, añadir autenticación básica a ciertas rutas o utilizar el protocolo HTTP/2.

Los servidores web no requieren una capacidad de cómputo elevada, para aprovechar al máximo la capacidad e una máquina y para no requerir un servidor web por cada sitio web se utilizan los servidores web virtuales. Un servidor web virtual es un servidor web que sirve el contenido de un sitio web normalmente asociado a un nombre de dominio aunque también es posible estar asociado a una dirección IP. Por ejemplo, empleando servidores web virtuales la misma instancia de servidor web puede servir el contenido de varios sitios web diferentes como site1.127.0.0.1.xip.io, site2.127.0.0.1.xip.io y site3.127.0.0.1.xip.io. El servidor web obtiene el nombre del dominio por el que es accedido y determina el servidor web al que redirigir la solicitud.

En cada servidor web virtual se puede configurar HTTPS y HTTP/2 para lo que es necesario generar un certificado autofirmado con el nombre del dominio.

En el ejemplo se usa un contenedor de Docker de modo que para probar los ejemplos no sea necesario instalar Nginx ni Apache.

Contenido del artículo

Configurar un servidor web virtual en Nginx

En el servidor web Nginx añadir un servidor web virtual consiste en añadir la configuración del sitio web en el directorio /etc/nginx/conf.d. La directiva relevante es server_name que asocia el servidor web virtual con el nombre del dominio. En base al nombre del dominio por el que se accede al servidor web se utiliza su configuración.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
server {
    listen 80;
    server_name www.127.0.0.1.xip.io;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
}

nginx-www.127.0.0.1.xip.io.conf
1
2
3
4
#!/usr/bin/env bash
docker run --rm -p 80:80 \
  -v `pwd`/nginx-www.127.0.0.1.xip.io.conf:/etc/nginx/conf.d/www.127.0.0.1.xip.io.conf:ro \
  nginx:latest
docker-nginx.sh

En el ejemplo se accede al sitio web por su nombre de dominio www.127.0.0.1.xip.io, usando el servidor de nombres xip.io que permite asociar un nombre de dominio a una dirección IP privada en este caso la dirección 127.0.0.1 que identifica a la propia máquina anfitrión.

Algunas distribuciones permiten añadir la configuración de los sitios web en los directorios /etc/nginx/sites-available, /etc/nginx/sites-enabled, /etc/apache2/sites-available, /etc/apache2/sites-enabled en el caso de las imágenes de Docker tanto para Nginx como para Apache estos directorios no están configurados por defecto.

En el ejemplo se usa la misma raíz de documentos que el sitio web para localhost, si se añaden archivos de contenido en una raíz de documentos propia para el servidor web virtual el contenido devuelto cambia o si cambia la raíz de documentos al directorio /usr/share/nginx que Nginx no tiene permitido el acceso se observa que cuando se accede al servidor por el nombre del dominio localhost se muestra la página de inicio y cuando se accede por el nombre del dominio www.127.0.0.1.xip.io se devuelve un error HTTP 403 Forbidden por falta de permisos.

Servidor web virtual en Nginx

Servidor web virtual en Nginx

Configurar un servidor web virtual en Apache HTTPD

En el servidor web Apache HTTPD un servidor web virtual se configura con sus propias directivas que igualmente indican el nombre del dominio asociado al servidor web y la ruta raíz de los recursos estáticos. Dependiendo del nombre del dominio por el que se accede al servidor web, se utiliza su configuración.

1
2
3
4
5
6
7
8
9
<VirtualHost *:80>
    ServerName wwww.127.0.0.1.xip.io
    DocumentRoot "/usr/local/apache2/htdocs"
 
    <Directory /usr/local/apache2/htdocs>
        Options -Indexes +FollowSymLinks
        AllowOverride All
    </Directory>
</VirtualHost>
httpd-vhosts.conf
1
2
3
4
5
#!/usr/bin/env bash
docker run --rm -p 80:80 \
  -v `pwd`/httpd.conf:/usr/local/apache2/conf/httpd.conf:ro \
  -v `pwd`/httpd-vhosts.conf:/usr/local/apache2/conf/extra/httpd-vhosts.conf:ro \
  httpd:alpine
docker-apache.sh

Usando Apache también se accede al sitio web por su nombre de dominio www.127.0.0.1.xip.io.

Servidor web virtual en Apache

Servidor web virtual en Apache
Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./docker-nginx.sh, ./docker-apache.sh

» Leer más, comentarios, etc...

Blog Bitix

Comandos para crear una autoridad de certificación (CA) con OpenSSL

agosto 07, 2020 01:00

Los certificados autofirmados no son suficientes para un entorno de producción. En producción hay que usar certificados firmados por una entidad de confianza. AWS Certificate Manager ofrece la suya pero tiene un coste elevado y los certificados de otras entidades tampoco son baratos. Para certificados de uso interno en una organización que proporcionan comunicaciones seguras OpenSSL permite con una serie de comandos crear una autoridad de certificación o CA en la que los servidores y clientes internos confíen. Las funciones de la CA incluyen crear certificados a partir de las solicitudes de los certificados para los servidores y también la revocación y renovación de certificados.

OpenSSL

Para usar el protocolo seguro HTTPS en un servidor web es necesario al menos generar un certificado autofirmado que incluya el dominio del sitio web. El certificado autofirmado proporciona comunicaciones seguras entre el servidor y cliente pero los clientes no lo consideran de confianza de modo que han de omitir la validación del certificado, un entorno de desarrollo o pruebas es suficiente pero en un escenario de producción para añadir más seguridad donde hay varios servidores que además requieren y validan también el certificado del cliente es necesario utilizar certificados generados por una entidad en la que se confíe, esta es la autoridad de certificación. La autoridad de certificación o CA es una entidad en la que se confía, si una CA ha firmado digitalmente un certificado esta asegura que el certificado pertenece a quien dice pertenecer. Las funciones de una CA son firmar los certificados que se le envían, de revocar los certificados cuando han sido comprometidos o de renovarlos cuando su validez expira.

AWS en su oferta de productos tiene AWS Certificate Manager que hace las funciones de autoridad de certificados, delega la complejidad de hacer las funciones de autoridad de certificados pero su inconveniente es que tiene el significativo precio de $400 mensuales más un pequeño coste por certificado emitido. La herramienta OpenSSL permite hacer las funciones de una autoridad de certificación con un conjunto de comandos.

Contenido del artículo

Crear la clave y certificado de la autoridad de certificación raíz

El certificado de una autoridad de certificación es un certificado autofirmado que se utilizar para comprobar que la firma de los certificados que emite la entidad es válida. Es necesario crear un par de claves asimétricas privada y pública y posteriormente generar su certificado.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env bash
set -e

CA_PATH=ca

export CA_DIR=.
export INTERMEDIATE_DIR=./intermediate

mkdir -p $CA_PATH/certs $CA_PATH/crl $CA_PATH/newcerts $CA_PATH/private $CA_PATH/intermediate/
cp openssl/ca/openssl*.conf $CA_PATH/
cp openssl/intermediate/openssl*.conf $CA_PATH/intermediate/
cd $CA_PATH

chmod 700 private
touch index.txt
echo 1000 > serial

# CA

openssl genrsa -out private/ca.key.pem 8192
chmod 400 private/ca.key.pem
ca-root-1.sh

El certificado de la CA raíz es simplemente un certificado autofirmado.

1
2
3
4
5
$ openssl req -config openssl.conf \
      -key private/ca.key.pem \
      -new -x509 -days 7300 -sha256 -extensions v3_ca \
      -subj "/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=Acme S.A (CA)" \
      -out certs/ca.cert.pem
ca-root-2.sh

Archivos y directorios de la autoridad de certificación con OpenSSL

Archivos y directorios de la autoridad de certificación con OpenSSL

Al crear el certificado se utiliza un archivo de configuración donde se guardan las opciones por defecto de configuración que determinan varios aspectos de OpenSSL como directorios, políticas, fecha de validez de los certificados emitidos, campos de los certificados que emite o perfiles de firma entre algunos otros.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# OpenSSL root CA configuration file.
# Copy to `/root/ca/openssl.cnf`.

[ ca ]
# `man ca`
default_ca = CA_default

[ CA_default ]
# Directory and file locations.
dir               = $ENV::CA_DIR
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
RANDFILE          = $dir/private/.rand
copy_extensions   = none

# The root key and root certificate.
private_key       = $dir/private/ca.key.pem
certificate       = $dir/certs/ca.cert.pem

# For certificate revocation lists.
crlnumber         = $dir/crlnumber
crl               = $dir/crl/ca.crl.pem
crl_extensions    = crl_ext
default_crl_days  = 30

# SHA-1 is deprecated, so use SHA-2 instead.
default_md        = sha256

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 1825
preserve          = no
policy            = policy_strict

[ policy_strict ]
# The root CA should only sign intermediate certificates that match.
# See the POLICY FORMAT section of `man ca`.
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ policy_loose ]
# Allow the intermediate CA to sign a more diverse range of certificates.
# See the POLICY FORMAT section of the `ca` man page.
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
# Options for the `req` tool (`man req`).
default_bits        = 8192
distinguished_name  = req_distinguished_name
string_mask         = utf8only

# SHA-1 is deprecated, so use SHA-2 instead.
default_md          = sha256

# Extension to add when the -x509 option is used.
x509_extensions     = v3_ca

[ req_distinguished_name ]
# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province Name
localityName                    = Locality Name
0.organizationName              = Organization Name
organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name
emailAddress                    = Email Address

# Optionally, specify some defaults.
countryName_default             = ES
stateOrProvinceName_default     = Spain
localityName_default            = Madrid
0.organizationName_default      =
organizationalUnitName_default  = Acme S.A. (CA)
emailAddress_default            =

[ v3_ca ]
# Extensions for a typical CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ usr_cert ]
# Extensions for client certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection

[ server_cert ]
# Extensions for server certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth

[ crl_ext ]
# Extension for CRLs (`man x509v3_config`).
authorityKeyIdentifier=keyid:always

[ ocsp ]
# Extension for OCSP signing certificates (`man ocsp`).
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, digitalSignature
extendedKeyUsage = critical, OCSPSigning
ca/openssl.conf

Crear la clave y certificado de la autoridad de certificación intermedia

De la confianza en la CA raíz depende toda la seguridad de los certificados que emite, que su clave privada y pública o certificado sean comprometidos es posiblemente lo peor que le puede ocurrir a una CA. Para minimizar el riesgo se intenta hacer el menor uso posible de la clave privada y pública de la autoridad de certificación raíz, para esto se suele crear una autoridad de certificación intermedia que es la que realmente firma, revoca y renueva los certificados para el servidor, clientes o usuarios. Si la CA intermedia es comprometida aún siendo también un problema grave de seguridad al menos la CA raíz puede crear una nueva CA intermedia en la que se pueda confiar.

La CA intermedia tiene su propio par de claves asimétricas privada y pública, también tiene su certificado que en este caso está firmado por la CA raíz.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Intermediate CA

mkdir -p intermediate/certs intermediate/crl intermediate/csr intermediate/newcerts intermediate/private
chmod 700 intermediate/private
touch intermediate/index.txt
echo 1000 > intermediate/serial
echo 1000 > intermediate/crlnumber

openssl genrsa -out intermediate/private/intermediate.key.pem 8192
chmod 400 intermediate/private/intermediate.key.pem
ca-intermediate-1.sh

La CA raíz firma únicamente el certificado de la CA intermedia. La CA intermedia generar la solicitud de firma de certificado, la CA raíz lo firma y se genera el certificado firmado.

1
2
3
4
$ openssl req -config intermediate/openssl.conf -new -sha256 \
      -key intermediate/private/intermediate.key.pem \
      -subj "/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=Acme S.A. (Intermediate CA)" \
      -out intermediate/csr/intermediate.csr.pem
ca-intermediate-2.sh

La CA recibe la solicitud de firma del certificado de la CA intermedia y la firma. La CA intermedia recibe las solicitudes de firma de certificados para los servidores, clientes y usuarios y genera los certificados firmados.

1
2
3
4
$ openssl ca -config openssl.conf \
      -extensions v3_intermediate_ca -days 3650 -batch -notext -md sha256 \
      -in intermediate/csr/intermediate.csr.pem \
      -out intermediate/certs/intermediate.cert.pem
ca-intermediate-3.sh

Una vez generado el certificado de la CA intermedia se puede inspeccionar el certificado y validar que la firma sea correcta utilizando el certificado de la CA raíz.

1
2
3
4
$ openssl x509 -noout -text \
      -in intermediate/certs/intermediate.cert.pem
$ openssl verify -CAfile certs/ca.cert.pem \
      intermediate/certs/intermediate.cert.pem
ca-intermediate-4.sh

Para validar los certificados emitidos por la CA intermedia se genera una cadena de certificados que incluye los certificados de la CA raíz y la CA intermedia, la cadena de certificados es simplemente la unión del contenido de ambos certificados.

1
2
3
$ cat intermediate/certs/intermediate.cert.pem \
      certs/ca.cert.pem > intermediate/certs/ca-chain.cert.pem
$ chmod 444 intermediate/certs/ca-chain.cert.pem
ca-intermediate-5.sh

La CA intermedia también utiliza su propio archivo de configuración de OpenSSL. En él se incluye la opción copy_extensions que permite copiar algunos atributos de la solicitud al certificado que genera, esto sirve para incluir en el certificado los múltiples Subject Alternative Name o SAN enviados en la solicitud. Si se permite emitir certificados con el mismo Subject es necesario utilizar la opción de configuración unique_subject = no.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# OpenSSL intermediate CA configuration file.
# Copy to `/root/ca/intermediate/openssl.cnf`.

[ ca ]
# `man ca`
default_ca = CA_default

[ CA_default ]
# Directory and file locations.
dir               = $ENV::INTERMEDIATE_DIR
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
RANDFILE          = $dir/private/.rand
copy_extensions   = copy

# The root key and root certificate.
private_key       = $dir/private/intermediate.key.pem
certificate       = $dir/certs/intermediate.cert.pem

# For certificate revocation lists.
crlnumber         = $dir/crlnumber
crl               = $dir/crl/intermediate.crl.pem
crl_extensions    = crl_ext
default_crl_days  = 30

# SHA-1 is deprecated, so use SHA-2 instead.
default_md        = sha256

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 1825
preserve          = no
policy            = policy_loose

[ policy_strict ]
# The root CA should only sign intermediate certificates that match.
# See the POLICY FORMAT section of `man ca`.
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ policy_loose ]
# Allow the intermediate CA to sign a more diverse range of certificates.
# See the POLICY FORMAT section of the `ca` man page.
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
# Options for the `req` tool (`man req`).
default_bits        = 8192
distinguished_name  = req_distinguished_name
string_mask         = utf8only

# SHA-1 is deprecated, so use SHA-2 instead.
default_md          = sha256

# Extension to add when the -x509 option is used.
x509_extensions     = v3_ca

[ req_distinguished_name ]
# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province Name
localityName                    = Locality Name
0.organizationName              = Organization Name
organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name
emailAddress                    = Email Address

# Optionally, specify some defaults.
countryName_default             = ES
stateOrProvinceName_default     = Spain
localityName_default            = Madrid
0.organizationName_default      = Acme S.A. (Intermediate CA)
organizationalUnitName_default  =
emailAddress_default            =

[ v3_ca ]
# Extensions for a typical CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ usr_cert ]
# Extensions for client certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection

[ server_cert ]
# Extensions for server certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth

[ client_server_cert ]
# Extensions for server certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = client, server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, serverAuth

[ crl_ext ]
# Extension for CRLs (`man x509v3_config`).
authorityKeyIdentifier=keyid:always

[ ocsp ]
# Extension for OCSP signing certificates (`man ocsp`).
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, digitalSignature
extendedKeyUsage = critical, OCSPSigning
intermediate/openssl.conf

Crear y firmar el certificado del servidor, clientes o usuarios

Para cada servidor, cliente de servidor o usuario hay que crear nuevamente su clave privada y pública, crear la solicitud de firma o Certificate Sign Request (CSR) que se envía a la autoridad de certificación intermedia para que lo firme y esta devuelva un certificado firmado. En el caso de un certificado para un servidor se incluye en el campo Common Name o CN el nombre del dominio del servidor, un certificado multidominio contiene varios nombres de dominio para los que es válido en el campo Subject Alternative Names o SAN, los certificados también se pueden asociar a una dirección IP concreta pero lo habitual es utilizar nombres de dominio.

Para crear el certificado firmado por la CA intermedia nuevamente el primer paso es generar la clave privada y pública y la solicitud de firma de certificado.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env bash
set -e

CA_PATH=../ca
CERTS_PATH=server-certs

export CA_DIR=$CA_PATH
export INTERMEDIATE_DIR=$CA_PATH/intermediate

mkdir -p $CERTS_PATH/certs $CERTS_PATH/csr $CERTS_PATH/private
cp openssl/server/openssl*.conf $CERTS_PATH/
cd $CERTS_PATH

# Server

openssl genrsa \
      -out private/consul-server.key.pem 8192
chmod 400 private/consul-server.key.pem

openssl req -config openssl-consul-server.conf \
      -key private/consul-server.key.pem \
      -subj "/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=Consul Server" \
      -new -sha256 -out csr/consul-server.csr.pem
server-certs-1.sh

Una vez la CA intermedia firma la solicitud y genera el certificado se puede validar que la firma sea correcta.

1
2
$ openssl verify -CAfile $CA_PATH/intermediate/certs/ca-chain.cert.pem \
      certs/consul-server.cert.pem
server-certs-2.sh

Los clientes de un servidor como un navegador web cuando establecen la conexión segura comprueban que el nombre del dominio al que se ha realizado la solicitud esté contenido en los nombres de dominio y direcciones IP del certificado devuelto por el servidor y que esté firmado por una CA en la que se confía.

Para incluir en un certificados múltiples SAN ya sean dominios y direcciones IP es necesario indicarlo en el archivo de configuración al crear la solicitud en la sección alt_names y habilitar la extensión req_ext.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[ req ]
default_bits       = 8192
distinguished_name = req_distinguished_name
req_extensions     = req_ext

[ req_distinguished_name ]
countryName                = Country Name (2 letter code)
stateOrProvinceName        = State or Province Name (full name)
localityName               = Locality Name (eg, city)
organizationName           = Organization Name (eg, company)
commonName                 = Common Name (e.g. server FQDN or YOUR name)

[ req_ext ]
subjectAltName = @alt_names

[alt_names]
DNS.1   = server.dc1.consul
DNS.2   = consul.service.consul
DNS.3   = localhost
DNS.4   = consul.192.168.33.10.xip.io
DNS.5   = consul.192.168.33.11.xip.io
DNS.6   = consul.192.168.33.12.xip.io
DNS.7   = consul.192.168.33.13.xip.io
DNS.8   = consul.192.168.33.14.xip.io
IP.1    = 127.0.0.1
IP.2    = 192.168.33.10
IP.3    = 192.168.33.11
IP.4    = 192.168.33.12
IP.5    = 192.168.33.13
IP.6    = 192.168.33.14
server/openssl-consul-server.conf

Inspeccionando la información del documento se observa que en la sección X509v3 Subject Alternative Name están incluidos los SAN adicionales.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
$ openssl x509 -noout -text \
      -in certs/consul-server.cert.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 4096 (0x1000)
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = ES, ST = Spain, O = Acme S.A., CN = Acme S.A. (Intermediate CA)
        Validity
            Not Before: Aug  6 22:39:48 2020 GMT
            Not After : Aug  5 22:39:48 2025 GMT
        Subject: C = ES, ST = Spain, L = Madrid, O = Acme S.A., CN = Consul Server
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (8192 bit)
                Modulus:
                    00:ef:5c:77:b4:e7:58:08:3f:d9:76:2c:67:52:f0:
                    37:c2:19:e6:e8:32:0e:b4:39:ea:77:b5:bb:e8:9c:
                    d0:75:62:90:d2:9d:53:bc:55:b1:4f:c8:09:69:41:
                    b1:8a:b4:39:5e:ba:8c:c5:b0:40:82:8c:4b:cb:f4:
                    b3:97:24:8d:3e:f6:81:24:80:7a:14:da:15:dc:8f:
                    ...
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Basic Constraints: 
                CA:FALSE
            Netscape Cert Type: 
                SSL Client, SSL Server
            Netscape Comment: 
                OpenSSL Generated Server Certificate
            X509v3 Subject Key Identifier: 
                40:A7:07:BE:51:F7:20:E6:6B:5C:0F:22:93:13:FF:07:9A:11:E5:E8
            X509v3 Authority Key Identifier: 
                keyid:0F:13:28:6E:13:8F:9D:25:7A:89:07:38:CA:05:F8:CA:49:47:57:62
                DirName:/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=Acme S.A (CA)
                serial:10:00

            X509v3 Key Usage: critical
                Digital Signature, Non Repudiation, Key Encipherment
            X509v3 Extended Key Usage: 
                TLS Web Client Authentication, TLS Web Server Authentication
            X509v3 Subject Alternative Name: 
                DNS:server.dc1.consul, DNS:consul.service.consul, DNS:localhost, DNS:consul.192.168.33.10.xip.io, DNS:consul.192.168.33.11.xip.io, DNS:consul.192.168.33.12.xip.io, DNS:consul.192.168.33.13.xip.io, DNS:consul.192.168.33.14.xip.io, IP Address:127.0.0.1, IP Address:192.168.33.10, IP Address:192.168.33.11, IP Address:192.168.33.12, IP Address:192.168.33.13, IP Address:192.168.33.14
    Signature Algorithm: sha256WithRSAEncryption
         12:15:c8:74:8d:e4:5b:8a:13:2c:ae:42:1a:ca:11:aa:c0:89:
         de:29:25:6c:4f:b4:52:24:1a:cd:25:18:55:dd:6d:9d:ea:12:
         d9:f1:5d:f5:46:75:39:43:b9:6d:c0:a3:49:a9:80:63:ba:ea:
         bc:24:3b:49:e8:c3:9b:60:51:fc:bb:52:a4:61:18:ac:fe:58:
         3c:61:2c:af:0d:83:fa:d9:e5:ad:fa:73:10:9c:5b:f1:72:13:
         ....
server-certs-3.sh

Añadir el certificado de la CA en el navegador Firefox

Los navegadores incluyen los certificados de algunas autoridades de certificados en las que se confía. En el caso de crear una CA propia como lo mostrado en este artículo y un certificado para un servidor firmado por esta autoridad de certificados el navegador mostrará una advertencia indicando que el certificado presentado no es de confianza antes de permitir entrar al sitio web, con este mensaje el usuario es consciente de que el certificado del servidor no es de confianza y si el usuario lo desea se permite el acceso al sitio web. Aún así el navegador muestra una advertencia en el icono de seguridad del sitio web de que hay un problema con el certificado.

Advertencia con certificado no de confianza en Firefox Advertencia con certificado no de confianza en Firefox

Advertencia con certificado no de confianza en Firefox

Para eliminar el mensaje de advertencia al acceder al sitio web y la advertencia del icono de seguridad del sitio web hay que instalar en el navegador el certificado de la CA raíz o intermedia. En el navegador web Firefox se importa un nuevo certificado de una CA en la que se confía desde la opción Preferencias > Privacidad y seguridad > Ver certificados > Autoridades > Importar en el navegador Chrome desde Configuración > Privacidad y seguridad > Gestionar certificados. Por defecto los navegadores web ya incorporan los certificados de varias CA importantes de internet.

Certificados de autoridades de certificación de confianza en Firefox

Certificados de autoridades de certificación de confianza en Firefox

Una vez se importa el certificado de la CA el navegador muestra el icono de seguridad del sitio web con el icono sin advertencias indicando que no hay ningún problema de seguridad en la conexión con el sitio web, tampoco se muestra la página inicial de advertencia.

Certificado de confianza y conexión segura en Firefox

Certificado de confianza y conexión segura en Firefox

Revocar y renovar un certificado

Además de firmar y generar de certificados las funciones de una CA es revocar un certificado cuando se ha comprometido y ya no es de confianza por un problema de seguridad y renovar de los certificados cuando se fecha de validez expira.

Una vez se revoca un certificado hay dos formas en la que la CA permite conocer si un certificado ha sido revocado, con Certificate Revocation List (CRL) o Online_Certificate_Status_Protocol (OCSP) aunque CRL ha quedado en desuso y se prefiere OCSP. El problema del método CRL es que hay una latencia entre el momento en que se realiza la revocación esta ventana de tiempo no es deseable para evitar problemas de seguridad con la mayor inmediatez, la revocación se publica en el registro de CRL, su segundo problema es que en una CA grande con un número grande de certificados revocados el registro de CRL será también grande. OCSP también permite conocer si un certificado ha sido revocado, evita la latencia de CRL ya que no depende de un registro y requiere menos tráfico para realizar la comprobación ya que no requiere la descargar del registro CRL, pero introduce otros problemas como que debe soportar una número enorme de peticiones de comprobación de certificado ya que se hace una petición por cada visita a un sitio web, en el caso de una CA importante que tiene un número de certificados emitidos que usan sitios con mucho tráfico de internet es un problema. El segundo problema es una pérdida de privacidad para los usuarios ya que la CA al recibir una solicitud de comprobación de certificado por cada visita podría rastrear a los usuarios. Para evitar o al menos mitigar en gran medida los problemas de OCSP se ha desarrollado OCSP Stapling.

La CA guarda los certificados que emite, con OpenSSL en la carpeta newcerts con un número de serie, en el archivo a modo base de datos index.txt almacena entre otras cosas el número de serie y el Subject del certificado emitido. El comando para revocar un certificado incluye como argumento el certificado a revocar y opcionalmente el motivo de la revocación. El archivo index.txt se actualiza y en la entrada asociada al certificado se indica una R para indicar que ha sido revocado.

Si la revocación del certificado se realiza por un problema de seguridad en el que la clave privada y pública asociada al certificado han sido comprometidas es necesario revocar el certificado comprometido, generar un nuevo par de claves para evitar suplantaciones de identidad y emitir un nuevo certificado generado a partir de las nuevas claves privada y pública.

1
2
openssl ca -config intermediate/openssl.conf \
    -revoke intermediate/newcerts/1000.pem -crl_reason superseded
ca-intermediate-revoke.sh
1
2
3
4
5
6
7
R	250805223948Z	25080528054Z	1000	unknown	/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=Consul Server
V	250805223951Z		1001	unknown	/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=Consul Agent
V	250805223953Z		1002	unknown	/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=Vault Server
V	250805223956Z		1003	unknown	/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=Vault Agent
V	250805223958Z		1004	unknown	/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=Nomad Server
V	250805224004Z		1005	unknown	/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=Nomad Agent
V	250805224009Z		1006	unknown	/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=UI Server
index.txt

Al revocar un certificado la CA regenera el CRL.

1
2
openssl ca -config intermediate/openssl.conf \
    -gencrl -out intermediate/crl/intermediate.crl
ca-intermediate-crl.sh

Los certificados se emiten con una de caducidad, llegado en el tiempo a la fecha de caducidad el certificado ya no se considera válido. En este caso hay que renovarlo. El servidor, cliente o usuario genera una nueva solicitud de certificado a partir de las mismas u otras nuevas claves privada y pública, la autoridad intermedia la recibe y realiza dos acciones: revoca el certificado anterior y genera un nuevo certificado a partir de la nueva solicitud de certificado. El comando de OpenSSL de la autoridad de certificados no permite que haya dos certificados con el mismo Subject válidos de modo que primero hay que revocar el certificado anterior. Los comandos son los mismos que los mostrados anteriormente para cada una de las acciones.

Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./ca.sh && ./server-certs.sh

» Leer más, comentarios, etc...

Blog Bitix

Comandos para crear certificados como una autoridad de certificación con OpenSSL

agosto 07, 2020 01:00

Los certificados autofirmados no son suficientes para un entorno de producción. En producción hay que usar certificados firmados por una entidad de confianza. AWS Certificate Manager ofrece la suya pero tiene un coste elevado y los certificados de otras entidades tampoco son baratos. Para certificados de uso interno en una organización que proporcionan comunicaciones seguras OpenSSL permite con una serie de comandos crear una autoridad de certificación o CA en la que los servidores y clientes internos confíen. Las funciones de la CA incluyen crear certificados a partir de las solicitudes de los certificados para los servidores y también la revocación y renovación de certificados.

OpenSSL

Para usar el protocolo seguro HTTPS en un servidor web es necesario al menos generar un certificado autofirmado que incluya el dominio del sitio web. El certificado autofirmado proporciona comunicaciones seguras entre el servidor y cliente pero los clientes no lo consideran de confianza de modo que han de omitir la validación del certificado, un entorno de desarrollo o pruebas es suficiente pero en un escenario de producción para añadir más seguridad donde hay varios servidores que además requieren y validan también el certificado del cliente es necesario utilizar certificados generados por una entidad en la que se confíe, esta es la autoridad de certificación. La autoridad de certificación o CA es una entidad en la que se confía, si una CA ha firmado digitalmente un certificado esta asegura que el certificado pertenece a quien dice pertenecer. Las funciones de una CA son firmar los certificados que se le envían, de revocar los certificados cuando han sido comprometidos o de renovarlos cuando su validez expira.

AWS en su oferta de productos tiene AWS Certificate Manager que hace las funciones de autoridad de certificados, delega la complejidad de hacer las funciones de autoridad de certificados pero su inconveniente es que tiene el significativo precio de $400 mensuales más un pequeño coste por certificado emitido. La herramienta OpenSSL permite hacer las funciones de una autoridad de certificación con un conjunto de comandos.

Contenido del artículo

Crear la clave y certificado de la autoridad de certificación raíz

El certificado de una autoridad de certificación es un certificado autofirmado que se utilizar para comprobar que la firma de los certificados que emite la entidad es válida. Es necesario crear un par de claves asimétricas privada y pública y posteriormente generar su certificado.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env bash
set -e

CA_PATH=ca

export CA_DIR=.
export INTERMEDIATE_DIR=./intermediate

mkdir -p $CA_PATH/certs $CA_PATH/crl $CA_PATH/newcerts $CA_PATH/private $CA_PATH/intermediate/
cp openssl/ca/openssl*.conf $CA_PATH/
cp openssl/intermediate/openssl*.conf $CA_PATH/intermediate/
cd $CA_PATH

chmod 700 private
touch index.txt
echo 1000 > serial

# CA

openssl genrsa -out private/ca.key.pem 8192
chmod 400 private/ca.key.pem
ca-root-1.sh

El certificado de la CA raíz es simplemente un certificado autofirmado.

1
2
3
4
5
$ openssl req -config openssl.conf \
      -key private/ca.key.pem \
      -new -x509 -days 7300 -sha256 -extensions v3_ca \
      -subj "/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=Acme S.A (CA)" \
      -out certs/ca.cert.pem
ca-root-2.sh

Al crear el certificado se utiliza un archivo de configuración donde se guardan las opciones por defecto de configuración que determinan varios aspectos de OpenSSL como directorios, políticas, fecha de validez de los certificados emitidos, campos de los certificados que emite o perfiles de firma entre algunos otros.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# OpenSSL root CA configuration file.
# Copy to `/root/ca/openssl.cnf`.

[ ca ]
# `man ca`
default_ca = CA_default

[ CA_default ]
# Directory and file locations.
dir               = $ENV::CA_DIR
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
RANDFILE          = $dir/private/.rand
copy_extensions   = none

# The root key and root certificate.
private_key       = $dir/private/ca.key.pem
certificate       = $dir/certs/ca.cert.pem

# For certificate revocation lists.
crlnumber         = $dir/crlnumber
crl               = $dir/crl/ca.crl.pem
crl_extensions    = crl_ext
default_crl_days  = 30

# SHA-1 is deprecated, so use SHA-2 instead.
default_md        = sha256

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 1825
preserve          = no
policy            = policy_strict

[ policy_strict ]
# The root CA should only sign intermediate certificates that match.
# See the POLICY FORMAT section of `man ca`.
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ policy_loose ]
# Allow the intermediate CA to sign a more diverse range of certificates.
# See the POLICY FORMAT section of the `ca` man page.
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
# Options for the `req` tool (`man req`).
default_bits        = 8192
distinguished_name  = req_distinguished_name
string_mask         = utf8only

# SHA-1 is deprecated, so use SHA-2 instead.
default_md          = sha256

# Extension to add when the -x509 option is used.
x509_extensions     = v3_ca

[ req_distinguished_name ]
# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province Name
localityName                    = Locality Name
0.organizationName              = Organization Name
organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name
emailAddress                    = Email Address

# Optionally, specify some defaults.
countryName_default             = ES
stateOrProvinceName_default     = Spain
localityName_default            = Madrid
0.organizationName_default      =
organizationalUnitName_default  = Acme S.A. (CA)
emailAddress_default            =

[ v3_ca ]
# Extensions for a typical CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ usr_cert ]
# Extensions for client certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection

[ server_cert ]
# Extensions for server certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth

[ crl_ext ]
# Extension for CRLs (`man x509v3_config`).
authorityKeyIdentifier=keyid:always

[ ocsp ]
# Extension for OCSP signing certificates (`man ocsp`).
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, digitalSignature
extendedKeyUsage = critical, OCSPSigning
ca/openssl.conf

Crear la clave y certificados de la autoridad de certificación intermedia

De la confianza en la CA raíz depende toda la seguridad de los certificados que emite, que su clave privada y pública o certificado sean comprometidos es posiblemente lo peor que le puede ocurrir a una CA. Para minimizar el riesgo se intenta hacer el menor uso posible de la clave privada y pública de la autoridad de certificación raíz, para esto se suele crear una autoridad de certificación intermedia que es la que realmente firma, revoca y renueva los certificados para el servidor, clientes o usuarios. Si la CA intermedia es comprometida aún siendo también un problema grave de seguridad al menos la CA raíz puede crear una nueva CA intermedia en la que se pueda confiar.

La CA intermedia tiene su propio par de claves asimétricas privada y pública, también tiene su certificado que en este caso está firmado por la CA raíz.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Intermediate CA

mkdir -p intermediate/certs intermediate/crl intermediate/csr intermediate/newcerts intermediate/private
chmod 700 intermediate/private
touch intermediate/index.txt
echo 1000 > intermediate/serial
echo 1000 > intermediate/crlnumber

openssl genrsa -out intermediate/private/intermediate.key.pem 8192
chmod 400 intermediate/private/intermediate.key.pem
ca-intermediate-1.sh

La CA raíz firma únicamente el certificado de la CA intermedia. La CA intermedia generar la solicitud de firma de certificado, la CA raíz lo firma y se genera el certificado firmado.

1
2
3
4
$ openssl req -config intermediate/openssl.conf -new -sha256 \
      -key intermediate/private/intermediate.key.pem \
      -subj "/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=Acme S.A. (Intermediate CA)" \
      -out intermediate/csr/intermediate.csr.pem
ca-intermediate-2.sh

La CA recibe la solicitud de firma del certificado de la CA intermedia y la firma. La CA intermedia recibe las solicitudes de firma de certificados para los servidores, clientes y usuarios y genera los certificados firmados.

1
2
3
4
$ openssl ca -config openssl.conf \
      -extensions v3_intermediate_ca -days 3650 -batch -notext -md sha256 \
      -in intermediate/csr/intermediate.csr.pem \
      -out intermediate/certs/intermediate.cert.pem
ca-intermediate-3.sh

Una vez generado el certificado de la CA intermedia se puede inspeccionar el certificado y validar que la firma sea correcta utilizando el certificado de la CA raíz.

1
2
3
4
$ openssl x509 -noout -text \
      -in intermediate/certs/intermediate.cert.pem
$ openssl verify -CAfile certs/ca.cert.pem \
      intermediate/certs/intermediate.cert.pem
ca-intermediate-4.sh

Para validar los certificados emitidos por la CA intermedia se genera una cadena de certificados que incluye los certificados de la CA raíz y la CA intermedia, la cadena de certificados es simplemente la unión del contenido de ambos certificados.

1
2
3
$ cat intermediate/certs/intermediate.cert.pem \
      certs/ca.cert.pem > intermediate/certs/ca-chain.cert.pem
$ chmod 444 intermediate/certs/ca-chain.cert.pem
ca-intermediate-5.sh

La CA intermedia también utiliza su propio archivo de configuración de OpenSSL. En él se incluye la opción copy_extensions que permite copiar algunos atributos de la solicitud al certificado que genera, esto sirve para incluir en el certificado los múltiples Subject Alternative Name o SAN enviados en la solicitud. Si se permite emitir certificados con el mismo Subject es necesario utilizar la opción de configuración unique_subject = no.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# OpenSSL intermediate CA configuration file.
# Copy to `/root/ca/intermediate/openssl.cnf`.

[ ca ]
# `man ca`
default_ca = CA_default

[ CA_default ]
# Directory and file locations.
dir               = $ENV::INTERMEDIATE_DIR
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
RANDFILE          = $dir/private/.rand
copy_extensions   = copy

# The root key and root certificate.
private_key       = $dir/private/intermediate.key.pem
certificate       = $dir/certs/intermediate.cert.pem

# For certificate revocation lists.
crlnumber         = $dir/crlnumber
crl               = $dir/crl/intermediate.crl.pem
crl_extensions    = crl_ext
default_crl_days  = 30

# SHA-1 is deprecated, so use SHA-2 instead.
default_md        = sha256

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 1825
preserve          = no
policy            = policy_loose

[ policy_strict ]
# The root CA should only sign intermediate certificates that match.
# See the POLICY FORMAT section of `man ca`.
countryName             = match
stateOrProvinceName     = match
organizationName        = match
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ policy_loose ]
# Allow the intermediate CA to sign a more diverse range of certificates.
# See the POLICY FORMAT section of the `ca` man page.
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
# Options for the `req` tool (`man req`).
default_bits        = 8192
distinguished_name  = req_distinguished_name
string_mask         = utf8only

# SHA-1 is deprecated, so use SHA-2 instead.
default_md          = sha256

# Extension to add when the -x509 option is used.
x509_extensions     = v3_ca

[ req_distinguished_name ]
# See <https://en.wikipedia.org/wiki/Certificate_signing_request>.
countryName                     = Country Name (2 letter code)
stateOrProvinceName             = State or Province Name
localityName                    = Locality Name
0.organizationName              = Organization Name
organizationalUnitName          = Organizational Unit Name
commonName                      = Common Name
emailAddress                    = Email Address

# Optionally, specify some defaults.
countryName_default             = ES
stateOrProvinceName_default     = Spain
localityName_default            = Madrid
0.organizationName_default      = Acme S.A. (Intermediate CA)
organizationalUnitName_default  =
emailAddress_default            =

[ v3_ca ]
# Extensions for a typical CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ v3_intermediate_ca ]
# Extensions for a typical intermediate CA (`man x509v3_config`).
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign

[ usr_cert ]
# Extensions for client certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = client, email
nsComment = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, emailProtection

[ server_cert ]
# Extensions for server certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth

[ client_server_cert ]
# Extensions for server certificates (`man x509v3_config`).
basicConstraints = CA:FALSE
nsCertType = client, server
nsComment = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth, serverAuth

[ crl_ext ]
# Extension for CRLs (`man x509v3_config`).
authorityKeyIdentifier=keyid:always

[ ocsp ]
# Extension for OCSP signing certificates (`man ocsp`).
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = critical, digitalSignature
extendedKeyUsage = critical, OCSPSigning
intermediate/openssl.conf

Crear y firmar el certificado del servidor, clientes o usuarios

Para cada servidor, cliente de servidor o usuario hay que crear nuevamente su clave privada y pública, crear la solicitud de firma o Certificate Sign Request (CSR) que se envía a la autoridad de certificación intermedia para que lo firme y esta devuelva un certificado firmado. En el caso de un certificado para un servidor se incluye en el campo Common Name o CN el nombre del dominio del servidor, un certificado multidominio contiene varios nombres de dominio para los que es válido en el campo Subject Alternative Names o SAN, los certificados también se pueden asociar a una dirección IP concreta pero lo habitual es utilizar nombres de dominio.

Para crear el certificado firmado por la CA intermedia nuevamente el primer paso es generar la clave privada y pública y la solicitud de firma de certificado.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env bash
set -e

CA_PATH=../ca
CERTS_PATH=server-certs

export CA_DIR=$CA_PATH
export INTERMEDIATE_DIR=$CA_PATH/intermediate

mkdir -p $CERTS_PATH/certs $CERTS_PATH/csr $CERTS_PATH/private
cp openssl/server/openssl*.conf $CERTS_PATH/
cd $CERTS_PATH

# Server

openssl genrsa \
      -out private/consul-server.key.pem 8192
chmod 400 private/consul-server.key.pem

openssl req -config openssl-consul-server.conf \
      -key private/consul-server.key.pem \
      -subj "/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=Consul Server" \
      -new -sha256 -out csr/consul-server.csr.pem
server-certs-1.sh

Una vez la CA intermedia firma la solicitud y genera el certificado se puede validar que la firma sea correcta.

1
2
$ openssl verify -CAfile $CA_PATH/intermediate/certs/ca-chain.cert.pem \
      certs/consul-server.cert.pem
server-certs-2.sh

Los clientes de un servidor como un navegador web cuando establecen la conexión segura comprueban que el nombre del dominio al que se ha realizado la solicitud esté contenido en los nombres de dominio y direcciones IP del certificado devuelto por el servidor y que esté firmado por una CA en la que se confía.

Para incluir en un certificados múltiples SAN ya sean dominios y direcciones IP es necesario indicarlo en el archivo de configuración al crear la solicitud en la sección alt_names y habilitar la extensión req_ext.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[ req ]
default_bits       = 8192
distinguished_name = req_distinguished_name
req_extensions     = req_ext

[ req_distinguished_name ]
countryName                = Country Name (2 letter code)
stateOrProvinceName        = State or Province Name (full name)
localityName               = Locality Name (eg, city)
organizationName           = Organization Name (eg, company)
commonName                 = Common Name (e.g. server FQDN or YOUR name)

[ req_ext ]
subjectAltName = @alt_names

[alt_names]
DNS.1   = server.dc1.consul
DNS.2   = consul.service.consul
DNS.3   = localhost
DNS.4   = consul.192.168.33.10.xip.io
DNS.5   = consul.192.168.33.11.xip.io
DNS.6   = consul.192.168.33.12.xip.io
DNS.7   = consul.192.168.33.13.xip.io
DNS.8   = consul.192.168.33.14.xip.io
IP.1    = 127.0.0.1
IP.2    = 192.168.33.10
IP.3    = 192.168.33.11
IP.4    = 192.168.33.12
IP.5    = 192.168.33.13
IP.6    = 192.168.33.14
server/openssl-consul-server.conf

Inspeccionando la información del documento se observa que en la sección X509v3 Subject Alternative Name están incluidos los SAN adicionales.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
$ openssl x509 -noout -text \
      -in certs/consul-server.cert.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 4096 (0x1000)
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = ES, ST = Spain, O = Acme S.A., CN = Acme S.A. (Intermediate CA)
        Validity
            Not Before: Aug  6 22:39:48 2020 GMT
            Not After : Aug  5 22:39:48 2025 GMT
        Subject: C = ES, ST = Spain, L = Madrid, O = Acme S.A., CN = Consul Server
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (8192 bit)
                Modulus:
                    00:ef:5c:77:b4:e7:58:08:3f:d9:76:2c:67:52:f0:
                    37:c2:19:e6:e8:32:0e:b4:39:ea:77:b5:bb:e8:9c:
                    d0:75:62:90:d2:9d:53:bc:55:b1:4f:c8:09:69:41:
                    b1:8a:b4:39:5e:ba:8c:c5:b0:40:82:8c:4b:cb:f4:
                    b3:97:24:8d:3e:f6:81:24:80:7a:14:da:15:dc:8f:
                    ...
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Basic Constraints: 
                CA:FALSE
            Netscape Cert Type: 
                SSL Client, SSL Server
            Netscape Comment: 
                OpenSSL Generated Server Certificate
            X509v3 Subject Key Identifier: 
                40:A7:07:BE:51:F7:20:E6:6B:5C:0F:22:93:13:FF:07:9A:11:E5:E8
            X509v3 Authority Key Identifier: 
                keyid:0F:13:28:6E:13:8F:9D:25:7A:89:07:38:CA:05:F8:CA:49:47:57:62
                DirName:/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=Acme S.A (CA)
                serial:10:00

            X509v3 Key Usage: critical
                Digital Signature, Non Repudiation, Key Encipherment
            X509v3 Extended Key Usage: 
                TLS Web Client Authentication, TLS Web Server Authentication
            X509v3 Subject Alternative Name: 
                DNS:server.dc1.consul, DNS:consul.service.consul, DNS:localhost, DNS:consul.192.168.33.10.xip.io, DNS:consul.192.168.33.11.xip.io, DNS:consul.192.168.33.12.xip.io, DNS:consul.192.168.33.13.xip.io, DNS:consul.192.168.33.14.xip.io, IP Address:127.0.0.1, IP Address:192.168.33.10, IP Address:192.168.33.11, IP Address:192.168.33.12, IP Address:192.168.33.13, IP Address:192.168.33.14
    Signature Algorithm: sha256WithRSAEncryption
         12:15:c8:74:8d:e4:5b:8a:13:2c:ae:42:1a:ca:11:aa:c0:89:
         de:29:25:6c:4f:b4:52:24:1a:cd:25:18:55:dd:6d:9d:ea:12:
         d9:f1:5d:f5:46:75:39:43:b9:6d:c0:a3:49:a9:80:63:ba:ea:
         bc:24:3b:49:e8:c3:9b:60:51:fc:bb:52:a4:61:18:ac:fe:58:
         3c:61:2c:af:0d:83:fa:d9:e5:ad:fa:73:10:9c:5b:f1:72:13:
         ....
server-certs-3.sh

Añadir el certificado de la CA en el navegador Firefox

Los navegadores incluyen los certificados de algunas autoridades de certificados en las que se confía. En el caso de crear una CA propia como lo mostrado en este artículo y un certificado para un servidor firmado por esta autoridad de certificados el navegador mostrará una advertencia indicando que el certificado presentado no es de confianza antes de permitir entrar al sitio web, con este mensaje el usuario es consciente de que el certificado del servidor no es de confianza y si el usuario lo desea se permite el acceso al sitio web. Aún así el navegador muestra una advertencia en el icono de seguridad del sitio web de que hay un problema con el certificado.

Advertencia con certificado no de confianza en Firefox Advertencia con certificado no de confianza en Firefox

Advertencia con certificado no de confianza en Firefox

Para eliminar el mensaje de advertencia al acceder al sitio web y la advertencia del icono de seguridad del sitio web hay que instalar en el navegador el certificado de la CA raíz o intermedia. En el navegador web Firefox se importa un nuevo certificado de una CA en la que se confía desde la opción Preferencias > Privacidad y seguridad > Ver certificados > Autoridades > Importar en el navegador Chrome desde Configuración > Privacidad y seguridad > Gestionar certificados. Por defecto los navegadores web ya incorporan los certificados de varias CA importantes de internet.

Certificados de autoridades de certificación de confianza en Firefox

Certificados de autoridades de certificación de confianza en Firefox

Una vez se importa el certificado de la CA el navegador muestra el icono de seguridad del sitio web con el icono sin advertencias indicando que no hay ningún problema de seguridad en la conexión con el sitio web, tampoco se muestra la página inicial de advertencia.

Certificado de confianza y conexión segura en Firefox

Certificado de confianza y conexión segura en Firefox

Revocar y renovar un certificado

Además de firmar y generar de certificados las funciones de una CA es revocar un certificado cuando se ha comprometido y ya no es de confianza por un problema de seguridad y renovar de los certificados cuando se fecha de validez expira.

Una vez se revoca un certificado hay dos formas en la que la CA permite conocer si un certificado ha sido revocado, con Certificate Revocation List (CRL) o Online_Certificate_Status_Protocol (OCSP) aunque CRL ha quedado en desuso y se prefiere OCSP. El problema del método CRL es que hay una latencia entre el momento en que se realiza la revocación esta ventana de tiempo no es deseable para evitar problemas de seguridad con la mayor inmediatez, la revocación se publica en el registro de CRL, su segundo problema es que en una CA grande con un número grande de certificados revocados el registro de CRL será también grande. OCSP también permite conocer si un certificado ha sido revocado, evita la latencia de CRL ya que no depende de un registro y requiere menos tráfico para realizar la comprobación ya que no requiere la descargar del registro CRL, pero introduce otros problemas como que debe soportar una número enorme de peticiones de comprobación de certificado ya que se hace una petición por cada visita a un sitio web, en el caso de una CA importante que tiene un número de certificados emitidos que usan sitios con mucho tráfico de internet es un problema. El segundo problema es una pérdida de privacidad para los usuarios ya que la CA al recibir una solicitud de comprobación de certificado por cada visita podría rastrear a los usuarios. Para evitar o al menos mitigar en gran medida los problemas de OCSP se ha desarrollado OCSP Stapling.

La CA guarda los certificados que emite, con OpenSSL en la carpeta newcerts con un número de serie, en el archivo a modo base de datos index.txt almacena entre otras cosas el número de serie y el Subject del certificado emitido. El comando para revocar un certificado incluye como argumento el certificado a revocar y opcionalmente el motivo de la revocación. El archivo index.txt se actualiza y en la entrada asociada al certificado se indica una R para indicar que ha sido revocado.

Si la revocación del certificado se realiza por un problema de seguridad en el que la clave privada y pública asociada al certificado han sido comprometidas es necesario revocar el certificado comprometido, generar un nuevo par de claves para evitar suplantaciones de identidad y emitir un nuevo certificado generado a partir de las nuevas claves privada y pública.

1
2
openssl ca -config intermediate/openssl.conf \
    -revoke intermediate/newcerts/1000.pem -crl_reason superseded
ca-intermediate-revoke.sh
1
2
3
4
5
6
7
R	250805223948Z	25080528054Z	1000	unknown	/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=Consul Server
V	250805223951Z		1001	unknown	/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=Consul Agent
V	250805223953Z		1002	unknown	/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=Vault Server
V	250805223956Z		1003	unknown	/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=Vault Agent
V	250805223958Z		1004	unknown	/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=Nomad Server
V	250805224004Z		1005	unknown	/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=Nomad Agent
V	250805224009Z		1006	unknown	/C=ES/ST=Spain/L=Madrid/O=Acme S.A./CN=UI Server
index.txt

Al revocar un certificado la CA regenera el CRL.

1
2
openssl ca -config intermediate/openssl.conf \
    -gencrl -out intermediate/crl/intermediate.crl
ca-intermediate-crl.sh

Los certificados se emiten con una de caducidad, llegado en el tiempo a la fecha de caducidad el certificado ya no se considera válido. En este caso hay que renovarlo. El servidor, cliente o usuario genera una nueva solicitud de certificado a partir de las mismas u otras nuevas claves privada y pública, la autoridad intermedia la recibe y realiza dos acciones: revoca el certificado anterior y genera un nuevo certificado a partir de la nueva solicitud de certificado. El comando de OpenSSL de la autoridad de certificados no permite que haya dos certificados con el mismo Subject válidos de modo que primero hay que revocar el certificado anterior. Los comandos son los mismos que los mostrados anteriormente para cada una de las acciones.

Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./ca.sh && ./server-certs.sh

» Leer más, comentarios, etc...

Blog Bitix

Configurar autenticación básica en los servidores web Nginx y Apache

agosto 02, 2020 08:00

Nginx

Apache HTTPD

La autenticación básica o basic auth es un mecanismo de autenticación sencillo que permite proteger los recursos solicitados de un sitio o aplicación web. Es fácil de configurar en el servidor web y está implementado en los propios navegadores, otra ventaja es que se puede añadir a un sitio o aplicación web sin necesidad de realizar modificaciones en su código.

La autenticación básica por seguridad requiere utilizar en el servidor web el protocolo seguro HTTPS y obtener un certificado para el nombre del dominio del servidor web ya que el navegador cuando envía al servidor el usuario y contraseña no los protege de forma especial y utiliza el mecanismo de comunicación de la conexión, es usual también configurar un servidor web virtual para aplicar esta configuración únicamente al sitio web deseado. Con el protocolo HTTPS el usuario y contraseña se transmite cifrada por la propia conexión segura.

Cuando un servidor para el acceso a un recurso requiere autenticación básica el navegador muestra una ventana emergente en la que se solicita un usuario y contraseña.

Solicitud de credenciales por el navegador con autenticación básica

Solicitud de credenciales por el navegador con autenticación básica
Contenido del artículo

Configurar autenticación básica en el servidor web Nginx

La autenticación básica en Nginx se activan añadiendo dos directivas, auth_basic y auth_basic_user_file, en el archivo de configuración del sitio web y recurso a proteger. En este caso con la raíz del sitio web / cualquier ruta está protegida con autenticación básica.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
server {
    listen 80;
    server_name localhost;

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2 default_server;
    server_name localhost;

    ssl_certificate conf.d/localhost.crt;
    ssl_certificate_key conf.d/localhost.key;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;

        auth_basic "Restricted Area";
        auth_basic_user_file conf.d/.localhost.htpasswd;
    }

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
        root /usr/share/nginx/html;
    }
}
nginx-default.conf

Utilizando Docker se puede crear un contenedor y probar la configuración. Al acceder a la dirección https://localhost el navegador mostrará el diálogo que solicita el usuario y contraseña.

1
2
3
4
5
6
7
#!/usr/bin/env bash
docker run --rm -p 80:80 -p 443:443 \
  -v `pwd`/nginx-default.conf:/etc/nginx/conf.d/default.conf:ro \
  -v `pwd`/localhost.crt:/etc/nginx/conf.d/localhost.crt:ro \
  -v `pwd`/localhost.key:/etc/nginx/conf.d/localhost.key:ro \
  -v `pwd`/.localhost.htpasswd:/etc/nginx/conf.d/.localhost.htpasswd:ro \
  nginx:alpine
docker-nginx.sh

Si los datos proporcionados no son correctos se devuelve el código de estado 401 que indica que el acceso no se ha autorizado y se requiere autorización. Si las credenciales son válidas se muestra el recurso solicitado.

Solicitud de credenciales Acceso al recurso solicitado

Solicitud de credenciales y acceso al recurso solicitado

Configurar autenticación básica en el servidor web Apache

El comando de Docker para Apache es similar e incluye el archivo de configuración del servidor virtual con el recurso protegido, la clave privada y el certificado que proporcionan cifrado en las comunicaciones con el protocolo HTTPS.

1
2
3
4
5
6
7
8
#!/usr/bin/env bash
docker run --rm -p 80:80 -p 443:443 \
  -v `pwd`/httpd.conf:/usr/local/apache2/conf/httpd.conf:ro \
  -v `pwd`/httpd-vhosts.conf:/usr/local/apache2/conf/extra/httpd-vhosts.conf:ro \
  -v `pwd`/localhost.crt:/usr/local/apache2/conf/extra/localhost.crt:ro \
  -v `pwd`/localhost.key:/usr/local/apache2/conf/extra/localhost.key:ro \
  -v `pwd`/.localhost.htpasswd:/usr/local/apache2/conf/extra/.localhost.htpasswd:ro \
  httpd:alpine
docker-apache.sh

En el caso del servidor web Apache las directivas necesarias a añadir en la configuración para activar la autenticación básica son: AuthType, AuthName, AuthUserFile y Require.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<VirtualHost *:80>
   ServerName localhost
   RewriteEngine On
   RewriteCond %{HTTPS} off
   RewriteRule (.*) https://%{SERVER_NAME}$1 [R,L]
</VirtualHost>

<VirtualHost *:443>
    ServerName localhost
    DocumentRoot /usr/local/apache2/htdocs

    SSLEngine on
    SSLCertificateFile conf/extra/localhost.crt
    SSLCertificateKeyFile conf/extra/localhost.key
    Protocols h2 http/1.1

    <Location "/">
        AuthType Basic
        AuthName "Restricted Area"
        AuthUserFile "conf/extra/.localhost.htpasswd"
        Require valid-user
    </Location>
</VirtualHost>

httpd-vhosts.conf

Solicitud de credenciales Acceso al recurso solicitado

Solicitud de credeciales y acceso al recurso solicitado

Cómo crear los archivos de credenciales htpasswd

La autenticación se realiza solicitando el navegador a petición del servidor un usuario y contraseña. En este caso el navegador presenta un diálogo al usuario que una vez introducidos realiza la petición de nuevo con las credenciales proporcionadas. El servidor para un recurso que requiere autenticación comprueba los datos de autenticación enviados y los valida con una pequeña base de datos en un archivo htpasswd que el administrador del servidor ha creado previamente con las credenciales de todos los usuarios.

Los archivos htpasswd que guardan las credenciales se crean con la utilidad de línea de comandos htpasswd. Tanto para añadir nuevos usuarios como para modificarlos y eliminarlos. Es posible guardar las contraseñas con diferentes algoritmos hash que transforman la contraseña en texto plano en un resultado equivalente con el cual no es posible conocer la contraseña original pero si validar que un dato proporcionado genera el mismo resultado con una función de una sola dirección. El algoritmo hash más seguro soportado es bcrypt que en el comando htpasswd se usa con la opción -B, los algoritmos MD5 y SHA con las capacidades de computación actuales se consideran inseguros.

1
2
3
4
5
6
7
8
# Crear un archivo de credenciales con un suaurio
htpasswd -c -b -B .localhost.htpasswd user password

# Añadir o modificar un usuario a un archivo de credenciales
htpasswd -b -B .localhost.htpasswd user password

# Eliminar una credencial
htpasswd -b -D .localhost.htpasswd user
htpasswd.sh

El resultado del archivo es una línea por credencial creada con el nombre del usuario y el hash de la contraseña.

1
user:$2y$05$bFhwns7nxUq.q2tmFf/Hp.ltqKKOEscuLuluis/72xGDXGI3FijmS
.localhost.htpasswd

Funcionamiento y cabecera de la autenticación básica

El formulario de autenticación solo se presenta una vez una vez introducidas unas credenciales correctas, dado que el protocolo HTTP es un protocolo sin estado el navegador en cada solicitud de cada página y recurso envía la cabecera Authorization que contiene el usuario y contraseña codificadas en base64. Esta cabecera y su valor aunque codificadas no tienen un mecanismo adicional de seguridad por eso es necesario utilizar el protocolo seguro HTTPS cuando se utiliza autenticación básica de modo que los datos incluidos en las cabeceras se transmitan cifrados.

Cabecera enviada en la petición con las credenciales de acceso al recurso

Cabecera enviada en la petición con las credenciales de acceso al recurso

La codificación en base64 no añade seguridad, el valor de la cabecera con las credenciales decodificadas contiene el usuario y la contraseña separados por dos puntos.

1
2
3
4
5
6
7
# Decodificación de base64
$ echo "dXNlcjpwYXNzd29yZA==" | base64 --decode
user:password

# Codificación en base64
$ echo "user:password" | base64
dXNlcjpwYXNzd29yZAo=
base64.sh

Con la herramienta de linea de comandos curl que permite realizar peticiones HTTP también hay que enviar la cabecera de autenticación para obtener el recurso.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ curl --insecure -H 'Authorization: Basic dXNlcjpwYXNzd29yZA==' 'https://localhost/'
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
curl.sh

El servidor web indica al navegador que el recurso solicitado requiere autenticación básica devolviendo en la respuesta el código de estado 401 Unauthorized y la cabecera www-authenticate.

Esquema de peticiones, cabeceras y respuestas del protocolo de autenticación básica

Esquema de peticiones, cabeceras y respuestas del protocolo de autenticación básica

Cuando no se envían credenciales o se envían unas incorrectas el navegador devuelve un código de estado 401.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
$ curl -v --insecure 'https://localhost/'
...
> GET / HTTP/2
> Host: localhost
> user-agent: curl/7.71.1
> accept: */*
> 
...
< HTTP/2 401 
< server: nginx/1.17.10
< date: Sun, 02 Aug 2020 10:38:14 GMT
< content-type: text/html
< content-length: 180
< www-authenticate: Basic realm="Restricted Area"
< 
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.17.10</center>
</body>
</html>
...

$ curl -v --insecure -H 'Authorization: Basic invalid' 'https://localhost/'
...
> GET / HTTP/2
> Host: localhost
> user-agent: curl/7.71.1
> accept: */*
> authorization: Basic invalid
> 
...
< HTTP/2 401 
< server: nginx/1.17.10
< date: Sun, 02 Aug 2020 10:37:03 GMT
< content-type: text/html
< content-length: 180
< www-authenticate: Basic realm="Restricted Area"
< 
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.17.10</center>
</body>
</html>
...
curl-authorization.sh

Alternativas a la autenticación básica

La ventaja de la autenticación básica es que es sencilla de implementar pero con algunos inconvenientes a tener en cuenta que son motivo de buscar alternativa más avanzadas aunque también mas complejas. Una desventaja es que los usuarios y contraseñas de las base de datos htpasswd son fijas, por seguridad las contraseñas se deberían rotar cada cierto tiempo para que usuarios que ya no deberían tener acceso tengan conocimiento de la contraseña válida actual, en una empresa es el caso de personas que ya no son empleados pero que si no se rotan las contraseñas seguirían teniendo acceso si no se implementan mecanismos de seguridad adicionales.

Otra desventaja de la autenticación básica es que los archivos htpasswd los crea el administrador de sistemas de modo que este tiene conocimiento de todas las contraseñas. Por esto la autenticación básica no es adecuada para realizar la autenticación en aplicaciones en las que los usuarios crean sus cuentas, las eliminan, tienen ellos mismos la posibilidad de cambian sus contraseñas y con la posibilidad de cerrar la sesión.

Estas desventajas hace de la autenticación básica no adecuada para algunos casos, dependiendo del caso las alternativas son implementar un formulario de autenticación, implementar OAuth en una aplicación web o utilizar autenticación mutua con certificados.

Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./docker-nginx.sh o ./docker-apache.sh

» Leer más, comentarios, etc...

Blog Bitix

Obtener un nombre de dominio para una dirección IP privada

julio 31, 2020 09:00

Las direcciones IP en la versión 4 del protocolo son un número de 32 bits, se representan en cuatro grupos de números decimales que toman el valor entre 0 y 255 separados por un punto, por ejemplo para la dirección que representa la máquina local 127.0.0.1. En la versión 6 del protocolo IP las direcciones IP son un número de 128 bits. Dado que los números de la direcciones IP son difíciles de recordar para los humanos se usan alias o sinónimos denominados nombres de dominio, por ejemplo, picodotdev.github.io o duckduckgo.com. El software que permite la conversión de nombre de dominio a dirección IP son los servidores DNS.

Algunos rangos de las direcciones IP están reservados para crear redes privadas no accesibles desde internet estos son de 10.0.0.0 a 10.255.255.255, de 172.16.0.0 a 172.31.255.255 y de 192.168.0.0 a 192.168.255.255. Para estas direcciones IP privadas en algunos casos también es necesario asociarlas con nombre de dominio, los servidores web virtuales que permiten alojar en el mismo servidor varios sitios web dependen del nombre del dominio a través del cual se realiza la petición, usar el protocolo seguro HTTPS y usar el protocolo HTTP/2 también depende de los nombres de dominio.

Para resolver nombres de dominio a direcciones IP hay dos posibilidades, la primera es utilizar el archivo /etc/hosts creando una línea con el nombre del dominio y su dirección IP, la segunda forma es crear y administrar un servidor DNS. Hay algunos servicios que ofrecen la funcionalidad de resolver dominios a direcciones IP sin necesidad de utilizar el archivo /etc_hosts ni crear un servidor DNS.

Los servidores DNS de xip.ip resuelven dominios a direcciones privadas IPv4 y sslip.io resuelve dominios a direcciones privadas IPv4 e IPv6. La utilidad de los nombres de dominio es hacer las direcciones IP más significativas incluyendo una cadena que las describa y permitir utilizar servidores virtuales en los servidores web sin utilizar el archivo etc/hosts ni un servidor DNS propio.

Estos servicios DNS permiten embeber en el nombre de dominio la dirección IP a la que resuelven cuando se les hace la consulta. Por ejemplo, para el nombre de dominio consul.192.168.33.10.xip.io resuelven a la dirección IP 192.168.33.10. El servicio sslip permite utilizar guiones en vez de puntos para separar los elementos de la dirección IP, por ejemplo consul.192-168-33-10.sslip.io.

Utilizando el comando dig se obtienen los registros DNS del nombre de dominio indicado incluído el registro A para la dirección IP.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
$ dig +short consul.192.168.33.10.xip.io
192.168.33.10

$ dig +short consul.192.168.33.20.xip.io
192.168.33.20

$ dig consul.192.168.33.10.xip.io

; <<>> DiG 9.16.5 <<>> consul.192.168.33.10.xip.io
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 59742
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0

;; QUESTION SECTION:
;consul.192.168.33.10.xip.io.	IN	A

;; ANSWER SECTION:
consul.192.168.33.10.xip.io. 292 IN	A	192.168.33.10

;; Query time: 3 msec
;; SERVER: 192.168.1.1#53(192.168.1.1)
;; WHEN: vie jul 31 11:08:13 CEST 2020
;; MSG SIZE  rcvd: 61

$ dig consul.192.168.33.10.sslip.io

; <<>> DiG 9.16.5 <<>> consul.192.168.33.10.sslip.io
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 52060
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;consul.192.168.33.10.sslip.io.	IN	A

;; ANSWER SECTION:
consul.192.168.33.10.sslip.io. 300 IN	A	192.168.33.10

;; Query time: 263 msec
;; SERVER: 192.168.1.1#53(192.168.1.1)
;; WHEN: vie jul 31 11:09:12 CEST 2020
;; MSG SIZE  rcvd: 74

$ dig consul.192-168-33-10.sslip.io

; <<>> DiG 9.16.5 <<>> consul.192-168-33-10.sslip.io
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26671
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;consul.192-168-33-10.sslip.io.	IN	A

;; ANSWER SECTION:
consul.192-168-33-10.sslip.io. 300 IN	A	192.168.33.10

;; Query time: 243 msec
;; SERVER: 192.168.1.1#53(192.168.1.1)
;; WHEN: vie jul 31 11:32:54 CEST 2020
;; MSG SIZE  rcvd: 74
dig.sh

» Leer más, comentarios, etc...

Picando Código

Private GitHub Header – Extensión de Firefox para diferenciar repositorios privados y públicos

julio 29, 2020 01:00

La única pista visual que tenemos de que un respositorio sea privado en GitHub es una etiqueta al lado del nombre. Para quienes pasamos mucho tiempo trabajando en GitHub, a veces corremos el riesgo de pensar que un grupo acotado de personas va a ver un mensaje, y podemos estar trabajando en un repositorio público sin darnos cuenta (y viceversa).

Greg Back, colega en Elastic, desarrolló esta extensión para Firefox que cambia el color del encabezado en la web de GitHub para que sea más claro: Private GitHub Header. La extensión se puede instalar desde el sitio web de Firefox Browser Add-Ons y el código fuente está disponible bajo la licencia MIT en GitHub: gtback/private-github-header.

El resultado ayuda mucho:

Encabezado GitHub en un repositorio público

Encabezado GitHub en un repositorio público

Encabezado GitHub en un repositorio privado

Encabezado GitHub en un repositorio privado

Una extensión sencilla pero súper práctica.

» Leer más, comentarios, etc...

Blog Bitix

Revertir un servicio a una versión anterior con Nomad

julio 26, 2020 07:00

Los errores no se planifican, se producen de forma inesperada. Además, un error en un entorno de producción es normalmente urgente e importante lo que obliga a cambiar las prioridades del equipo dejando lo que están haciendo y ocuparse de resolver el problema. En ocasiones no será posible resolver el problema y la única solución es revertir la versión de la aplicación a la anterior. Dependiendo de la automatización de los procesos incluso el volver a la versión anterior quizá sea complicado. Los errores no se planifican pero si se puede planificar estar preparado para algunos errores, una forma de estar preparado ante errores es tener un proceso y herramientas para volver a la versión anterior rápido y fácilmente.

Nomad

HashiCorp

En el desarrollo de una aplicación se dedica un considerable esfuerzo a tener la mayor seguridad que los cambios no introducen errores, se realizan numerosas pruebas unitarias, pruebas de integración, si es una aplicación web pruebas funcionales simulando la interacción de un usuario. Todas estas pruebas se ejecutan además del equipo de cada desarrollador en un entorno de integración continua o CI en cada commit al repositorio de control de versiones del código fuente, el equipo de desarrollo recibe una notificación con inmediatez en caso de fallar alguna prueba. Aún se hacen mas pruebas algunas manuales en un entorno de staging que tiene una configuración similar al de producción. Finalmente, se despliega la versión validada en producción todavía haciendo más comprobaciones con estrategias de despliegue blue/green y canary, si no se ha descubierto ningún error se promociona la versión en todas las instancias.

Y aún con todo estos procesos de pruebas en ocasiones se descubren errores en producción. Si la causa del error se descubre rápido y es fácil de solucionar se opta por corregirlo, crear una nueva versión y desplegarla. Si el error es leve y no afecta a una funcionalidad importante se puede esperar a la siguiente versión para corregirlo. Pero algunos errores impactan en una funcionalidad vital para el negocio, tanto que obligan al equipo a dejar las tareas planificadas que están haciendo aún siendo también importantes para descubrir la causa del error y corregirlo, a veces la causa y la corrección se realiza rápido. En otras ocasiones la causa es difícil de determinar o la corrección es compleja, en estas ocasiones la opción adecuada es volver a la versión anterior buena conocida haciendo un revert o rollback. Se pierden las otras nuevas funcionalidades incorporadas en la misma versión pero se evita hacer cambios que introducen deuda técnica por la presión de dar solución rápido al error y se evita al equipo trabajar en un desagradable bajo presión.

En la operación de sistemas lo ideal es que todas las tareas estén automatizadas para evitar errores al realizar operaciones manuales y para evitar complejos procesos que impidan a cualquier miembro de equipo realizar la tarea por falta de conocimiento, complejidad o requerir formación. Dos de estas tareas a automatizar son el despliegue de una aplicación con una nueva versión y también el revertir a una versión anterior en caso de detectar que la nueva versión tiene algún tipo de error.

Nomad es un orquestador de servicios que en gran medida automatiza el despliegue de de las aplicaciones, su actualización a nuevas versiones y revertir a versiones anteriores.

Se inicia con el siguiente comando.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$ nomad agent -dev
==> No configuration files loaded
==> Starting Nomad agent...
==> Nomad agent configuration:

       Advertise Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
            Bind Addrs: HTTP: 127.0.0.1:4646; RPC: 127.0.0.1:4647; Serf: 127.0.0.1:4648
                Client: true
             Log Level: DEBUG
                Region: global (DC: dc1)
                Server: true
               Version: 0.12.0

==> Nomad agent started! Log data will stream in below:

...
nomad-start.sh

Para ejecutar un servicio basta con enviar su definición en su archivo de configuración con un comando.

1
2
3
4
5
6
7
$ nomad run service-1.nomad
==> Monitoring evaluation "56421393"
    Evaluation triggered by job "service"
    Allocation "b51101cc" created: node "5721462c", group "service"
    Evaluation within deployment: "33154ac5"
    Evaluation status changed: "pending" -> "complete"
==> Evaluation "56421393" finished with status "complete"
nomad-run-1.sh

La definición del servicio en Nomad incluye la versión del artecfacto a desplegar. En este caso el servicio es simplemente un comando que emite una traza en la consola, a efectos didácticos no es relevante ya que sería lo mismo si fuera el comando de inicio de la aplicación Java que incluya la versión del jar o si el contenedor Docker estuviese versionado.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
job "service" {
  datacenters = ["dc1"]

  group "service" {

    task "service" {
      driver = "docker"

      config {
        image = "busybox:latest"
        command = "ash"
        args = [
          "-c",
          "while true; do echo \"Version 1\"; sleep 1; done"
        ]
      }
    }
  }
}
service-1.nomad

El servicio emite en la salida el mensaje.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$ nomad job status service
...
Allocations
ID        Node ID   Task Group  Version  Desired  Status    Created     Modified
b51101cc  5721462c  service     0        stop     running   40m6s ago   38m31s ago
$ nomad alloc logs b51101cc
Version 1
Version 1
Version 1
Version 1
Version 1
Version 1
Version 1
Version 1
...
service-1-logs.sh

Al hacer cambios en el código de un servicio con nuevas características, correcciones de errores o mejoras de las funcionalidades existentes hay que generar una nueva versión del artefacto a desplegar, en el caso de una aplicación Java con Spring Boot puede ser un nuevo archivo jar con un nuevo nombre que incluye su versión, el artefacto de despligue es un contenedor Docker este también estará versionado. En la definición del servicio para Nomad hay que actualizar la referencia del artefacto a la nueva versión. El comando para realizar el despliegue es el mismo, basta con enviar la nueva definición del servicio a Nomad.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
job "service" {
  datacenters = ["dc1"]

  group "service" {

    task "service" {
      driver = "docker"

      config {
        image = "busybox:latest"
        command = "ash"
          args = [
            "-c",
            "while true; do echo \"Version 2 (error)\"; sleep 1; done"
        ]
      }
    }
  }
}
service-2.nomad

Como una nueva versión implica cambios en el código o la configuración del servicio es posible introducir nuevos errores que aún con todos los procesos de pruebas en diferentes entornos comentados sean descubiertos solo en el entorno de producción como es el típico NullPointerException o en este caso un mensaje de traza erróneo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$ nomad job status service
...
Allocations
ID        Node ID   Task Group  Version  Desired  Status    Created     Modified
f7bd853e  5721462c  service     1        stop     running   38m33s ago  34m57s ago
b51101cc  5721462c  service     0        stop     complete  40m6s ago   38m31s ago
$ nomad alloc logs f7bd853e
Version 2 (error)
Version 2 (error)
Version 2 (error)
Version 2 (error)
Version 2 (error)
Version 2 (error)
Version 2 (error)
Version 2 (error)
...
service-2-logs.sh

Para la tarea de volver a una versión anterior el proceso con Nomad es igualmente sencillo, Nomad recuerda las definiciones anteriores enviadas con lo que solo se necesita utilizar el comando para restaurar la definición del servicio. Con los artefactos de despliegue versionados ya sea con contenedores Docker o con versiones en archivos jar las definiciones de los servicios contienen la referencia de los artefactos que utilizan.

1
2
3
4
5
6
7
$ nomad job revert service 0
==> Monitoring evaluation "493bcc52"
    Evaluation triggered by job "service"
    Evaluation within deployment: "e13303e3"
    Allocation "fc4fd574" created: node "5721462c", group "service"
    Evaluation status changed: "pending" -> "complete"
==> Evaluation "493bcc52" finished with status "complete"
nomad-revert.sh

El volver a la versión anterior puede hacerse desde la consola gráfica de administración de Nomad o desde la interfaz de línea de comandos introduciendo la definición anterior del servicio. Nomad se encarga de actualizar a la versión anterior de todas las instancias de forma progresiva utilizando estrategias de despliegue blue/green y canary con Nomad que haya del servicio de forma automatizada y el servicio vuelve a la normalidad.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$ nomad job status service
...
Allocations
ID        Node ID   Task Group  Version  Desired  Status    Created     Modified
fc4fd574  5721462c  service     2        run      running   34m58s ago  34m43s ago
f7bd853e  5721462c  service     1        stop     complete  38m33s ago  34m57s ago
b51101cc  5721462c  service     0        stop     complete  40m6s ago   38m31s ago
$ nomad alloc logs fc4fd574
Version 1
Version 1
Version 1
Version 1
Version 1
Version 1
Version 1
Version 1
...
service-3-logs.sh

Desde la consola web de administración también se ven las definiciones de los jobs y sus versiones con sus diferencias.

Versión 1 del servicio Versión 2 del servicio Versiones del servicio

Versiones del servicio

» Leer más, comentarios, etc...

Bitácora de Javier Gutiérrez Chamorro (Guti)

Estadísticas de comentarios

julio 25, 2020 05:51



En 17.000 comentarios, Fernando sugería hacer una análisis de los comentarios, una especie de ranking mostrando los más antiguos, los que más participan, etcétera. Me pareció un ejercicio curioso, y una buena excusa para lanzar unas cuantas consultas a la base de datos de WordPress. Aquí tenéis los resultados. Comentaristas más antiguos Autor Fecha del …

Estadísticas de comentarios Leer más »



Artículo publicado originalmente en Bitácora de Javier Gutiérrez Chamorro (Guti)

» Leer más, comentarios, etc...

Arragonán

Disponible para ayudar a equipos

julio 19, 2020 12:00

Ya sea desde el rol de mentor o de asesor, me encanta ayudar a equipos en todo el espectro que involucra el desarrollo de producto digital.

Esta inquietud tardó en despertar en mí, pero ha aumentado con el paso de los años. Arrancó primero con la iniciativa de Senpai Devs mentorizando a varias personas. Después haciendo algunas formaciones e intervenciones para ayudar a startups y corporaciones junto a mis compañeros de Coding Stones. Más tarde en Inditex donde, además de ejercer como tech-lead en mis equipos, pude aconsejar a algún otro equipo más. Incluso desde unos meses dedico algo de mi tiempo libre a hacer asesorías puntuales.

Ahora, tras mi paso por Devengo, quiero dedicar más tiempo a ayudar a equipos a crear mejor software y producto digital. De hecho, ya estoy cerrando fechas para arrancar un par de colaboraciones.

Haciendo una sesión de mob programming con código proyectado en la pared

¿Ayudar a equipos? ¿Cómo?

Lo de ayudar a equipos quizás queda algo abstracto. Principalmente las actividades que quiero hacer son mentorizar y aconsejar a equipos. Además de esto, una vez identificadas sus necesidades, preparar formaciones y talleres para que adquireran las bases de ciertas prácticas.

Mi intención es ayudar a mejorar extremo a extremo en la creación de producto digital: en el trabajo en equipo, en las prácticas técnicas y en la gestión de producto.

¿En qué puedo ayudar a tu organización o equipo?

Algunos dolores con los que puedo aportar valor son:

  • Habéis empezado a trabajar con procesos ágiles como scrum o kanban y los equipos siguen sin tener los resultados que esperabais. O tal vez estéis pensando en empezar con ello pero necesitáis ayuda para establecer las bases.
  • Tenéis problemas con la capacidad y calidad de entrega, porque en las revisiones, o incluso en producción, se encuentran excesivos bugs. Motivo por el cual queréis incorporar o mejorar en algunas prácticas técnicas. O necesitáis ayuda para identificar qué mejorar.
  • A los equipos les cuesta entregar. Se eternizan las historias de usuario en doing, o incluso quedan semanas bloqueadas.
  • Hay cierto desalineamiento, o incluso tensiones, a la hora de integrar a los diferentes roles del equipo. Típicamente cualquier permuta entre desarrolladores vs diseñadores vs product owners/managers vs administradores de sistemas vs…
  • Se ha acumulado demasiada deuda técnica, así que el legacy no os deja avanzar al ritmo esperado por el negocio.
  • No tenéis muy claro cómo cambiar la mentalidad de trabajar con enfoque a proyecto hacia enfoque a producto, tanto del propio equipo como por parte de stakeholders. Hay más preocupación y presión por cumplir con estimaciones que por lograr objetivos.
  • El negocio va como un tiro y estáis creciendo, eso implica cambiar parte de la arquitectura existente y analizar si hay que reorganizar los diferentes equipos que han ido surgiendo, incluso la forma de esos equipos.
  • En la organización se ve a los equipos de producto o tecnología como un impedimento para nuevas iniciativas y oportunidades de negocio, no como un colaborador y facilitador.

Si identificas alguno de esos problemas en tu equipo u organización, por mi parte estaría encantado de que hablemos para ver si encontramos una forma de colaborar.

» Leer más, comentarios, etc...

Fixed Buffer

¡A descansar!

julio 14, 2020 06:04

Tiempo de lectura: < 1 minuto

Un año más llega el veranito y con él las vacaciones. Es momento de coger aire, descansar y recuperar fuerzas para seguir adelante. Este año debido a los problemas de movilidad por esta pandemia mundial me ha pillado el toro y repito foto :(.

He de decir que la casa está terminada (recordemos que a eso me dediqué el verano pasado) y a la vuelta de vacaciones prometo actualizar la foto por la de la casita terminada 🙂

Dicho esto, es hora de decir adiós hasta septiembre, momento en el que volveremos a la carga con unas ideas a las que estoy dando forma y que espero poner en marcha antes del segundo aniversario de FixedBuffer. Además de eso, ya me conocéis, no puedo parar, así que estoy preparando contenido para algunos otros blogs con los que colaboro así que estad atentos a la sección de colaboraciones 🙂

Muchas gracias a todos los que hacéis que este proyecto siga adelante y nos vemos en septiembre

**La entrada ¡A descansar! se publicó primero en Fixed Buffer.**

» Leer más, comentarios, etc...

Israel Perales

Liberar espacio en Nexus Repository

julio 11, 2020 01:44

Liberar espacio en Nexus Repository

Actualmente me encuentro trabajando(Ya no paso mucho tiempo desde que escribí el borrador) en un ambiente de integración continua que involucra Gitlab, Jenkins, SonarQube y Nexus (3), todo esto esta en un solo servidor y digamos que cada uno consume su porción de los recursos.

Los que mas disco duro consumen, son Jenkins y Nexus, en esta ocasión voy a explicar como libere casi 40GB en una instancia que pesaba 92GB.

Primero que nada decir que Nexus tiene una interfaz basada en Extjs, que en esta época la hace horrible a los ojos, mas aun la funcionalidad, en la cual no es posible eliminar imágenes de docker usando la multi selección, al menos por la interfaz de usuario es una tarea casi imposible.

Después de tratar de eliminar todas esas imagenes viejas por la interfaz durante 5 minutos, recordé que Nexus tiene una API REST y me decidí a leerla y jugar con los endpoints que tiene.

También pensé que alguien mas paso por lo mismo y googleando un poco encontré un post y un repositorio de Github en el que se tiene un utilidad de linea de comandos que me podría ayudar.

Descargamos lo necesario siguiendo las instrucciones del repositorio de github

wget https://s3.eu-west-2.amazonaws.com/nexus-cli/1.0.0-beta/linux/nexus-cli

./nexus-cli configure

./nexus-cli image ls

./nexus-cli image delete -keep 1 -name imagen

Después de esto NO se debería ver espacio liberado, ya que Nexus solo marca los archivos como candidatos a removerse, se debe entrar a la sección de administrador y crear una tarea de compactación del blob y ejecutarla.

Fuentes:

» Leer más, comentarios, etc...

Israel Perales

Comprar una alberca.

julio 11, 2020 01:17

Comprar una alberca.

Este es un post que no tiene nada que ver con tecnología, pero he visto muchas personas en grupos de Facebook que son estafadas en estas temporadas al comprar una alberca, espero esta lista pueda servirles para hacer sus compras mas seguras y darse una idea del precio en pesos mexicanos:

Alberca

Comprar una alberca.

Alberca Avenli 4383 Litros
Precio: $2,299.00

https://www.coppel.com/alberca-avenli-4383-litros-im-5706313-0


Bomba filtro de agua

Comprar una alberca.

Bomba de Filtración 530Gph 110-120V -Intex
Precio: $955.00

https://www.juliocepeda.com/bomba-de-filtracion-530gph-110-120v-intex.html


Cubierta para alberca

Comprar una alberca.

Cubierta Para Alberca -Intex
Precio: $399.00

https://www.juliocepeda.com/cubierta-para-alberca-intex.html


Kit analizador de PH

Comprar una alberca.

Juego Analiza Cloro Y Ph De Alberca Por Goteros Kit
Precio: $328

https://articulo.mercadolibre.com.mx/MLM-631159268-juego-analiza-cloro-y-ph-de-alberca-por-goteros-kit-_JM

También se encuentra en Home Depot aun precio mas bajo, solo que no tenían disponibles.

https://www.homedepot.com.mx/mantenimiento-de-albercas/juego-para-analizar-cloro-y-ph-850362


Inflable salvavidas

Comprar una alberca.

Inflable Salvavidas Flotador Para Bebé Con Techo Andadera
Precio: $249

https://articulo.mercadolibre.com.mx/MLM-668198000-inflable-salvavidas-andadera-con-techo-para-bebe-intex-_JM


Dosificador de cloro

Comprar una alberca.

Dosificador Flotante Para Tabletas De Cloro Humboldt- Blanco con Azul
Precio: $95.00
Precio con envió: $134.00

Aun y con el envió fue mucho mas barato comprarlo en Linio que en otras tiendas, también revise directamente en el proveedor principal pero el costo del envió era mucho mayor.

https://www.linio.com.mx/p/dosificador-flotante-para-tabletas-de-cloro-humboldt-blanco-con-azul-ykdslq


Tabletas de cloro

Comprar una alberca.

TABLETAS DE CLORO 1 KG AQUA POOL
Precio: $219

Este articulo no lo compre en linea ya que Home Depot tiene costos muy elevados en sus envíos y me queda a 15 min de mi casa y decidí ir por el.

https://www.homedepot.com.mx/jardin/albercas/mantenimiento-de-albercas/tableta-de-tricloro-estabilizado-3-1-kg-244192


Acido muriatico

Comprar una alberca.

Alamo · Ácido muriático
Precio: $24.00

De este producto compre 2 litros, es necesario para bajar el PH del agua que de forma natural va subiendo.

https://web.cornershopapp.com/store/25/search/acido muriatico/product/336915


Conclusión

En total fue un gasto de $4631 pesos repartidos en 6 tiendas:

Estos son algunos de los principales articulos para una alberca desmontable pequeña, sin contar los filtros de la bomba que no he comprado por que pienso hacer uno casero.

» Leer más, comentarios, etc...

Variable not found

await Task.Delay(3888000000); // ¡Vacaciones!

julio 09, 2020 06:05

La palabra vacaciones, derivada del latín vacans, participio del verbo vacare (estar libre, desocupado), está definida por la RAE como "Período del año en el que los trabajadores descansan temporalmente del trabajo". Y creo que ha llegado la hora de aplicarla, aunque no sea en toda su extensión :)

Como es habitual por estas fechas, os informo que la semana que viene iniciaré mi descanso veraniego, y el blog quedará en modo de bajo consumo hasta septiembre. Durante este tiempo seguiré trabajando en otras cosas (tengo grandes proyectos en curso, ya os iré contando ;)) pero al menos podré hacer jornadas más cortas y descansar un poco los fines de semana, que falta hace.

¡Nos vemos a la vuelta!

Playa
Playa de Costa Ballena, Rota (Cádiz). Imagen: Hotel Elba

Publicado en Variable not found.

» Leer más, comentarios, etc...

Variable not found

Cómo invocar métodos de instancia C# desde Javascript con Blazor (interop 3/3)

julio 08, 2020 06:12

BlazorEn esta serie sobre interoperación Javascript-Blazor hemos ido viendo cómo desde Blazor Server o WebAssembly podíamos llamar a funciones Javascript disponibles en el browser, y también cómo conseguir invocar métodos estáticos .NET desde Javascript.

Como recordaréis del post anterior, la invocación de métodos estáticos era bastante sencilla, porque básicamente desde Javascript sólo teníamos que conocer el nombre del ensamblado donde se encontraba el código y el nombre del método a ejecutar. El hecho de que el método fuera estático es una ventaja, pues no hay "piezas móviles" en el puzzle.

Sin embargo, si desde Javascript queremos invocar un método de instancia, la cosa se complica un poco porque tendremos que ayudar a Blazor a determinar de qué instancia se trata, y esto a priori no suena sencillo porque Javascript y Blazor viven en dos mundos diferentes (de hecho, en el caso de Blazor Server incluso están físicamente separados). Sin embargo, veremos que las herramientas que ofrece el framework son suficientes para llevarlo a cabo sin liarnos demasiado.

Esta última entrega la dedicaremos precisamente a esto: aprender cómo podemos llamar desde Javascript a métodos de instancia escritos en C#.

¿Cómo invocar métodos de instancia C# desde Javascript?

Como hemos dejado entrever, si queremos llamar desde Javascript a un método de instancia escrito en C#, tanto en Blazor Server como en WebAssembly, lo que debemos hacer es:
  1. Desde C#, obtener una referencia hacia la instancia sobre la que se ejecutará el método que queremos invocar desde Javascript.
     
  2. Hacer llegar a Javascript la referencia obtenida anteriormente. Lo habitual será enviársela mediante una llamada (interop) desde C# a Javascript.
     
  3. Ya desde el lado Javascript, utilizar la referencia para invocar al método deseado, que debe ser público y estar decorado con el atributo [JSInvokable] que ya conocemos.
     
  4. Cuando ya no sea necesaria, liberar la referencia utilizada anteriormente llamando a su método Dispose() para evitar fugas de memoria.
El proceso es exactamente igual para Blazor Server que para Blazor WebAssembly, por lo que no distinguiremos entre ambos.

Veamos cada uno de estos puntos.

1. Obtener la referencia hacia la instancia

La referencia a la instancia debemos crearla desde C# utilizando el método DotNetObjectReference.Create(), suministrándole la instancia que queremos referenciar. Por ejemplo, el siguiente código en el interior de un componente crearía una referencia hacia el mismo:
var reference = DotNetObjectReference.Create(this);
Pero en realidad, la referencia podríamos crearla de cualquier objeto que esté en memoria en ese momento. Puede ser el propio componente en el que estamos, o bien un helper (de hecho, en la documentación oficial de Microsoft es el ejemplo que aparece), o cualquier otra cosa. La cuestión es que esa variable reference contendrá una instancia de DotNetObjectReference<T>, siendo T el tipo de objeto referenciado.

Como más adelante tendremos que liberar la referencia, el ideal es dejarla almacenada en un campo para que luego podamos liberarla llamando a su método Dispose().
@code {
DotNetObjectReference<MyClass> _reference;
...
// En el interior de algún método asignamos el campo:
_reference = DotNetObjectReference.Create(myObject);
...
}

2. Hacer llegar a Javascript la referencia

Para que el lado Javascript pueda invocar nuestro método, debemos hacerle llegar la referencia obtenida anteriormente, es decir, el objeto DotNetObjectReference<T>. Como hemos comentado, lo habitual será enviarla mediante interop desde C# a Javascript, algo que ya sabemos hacer :)

Por ejemplo, en el siguiente código invocamos una función personalizada interopDemo.initialize() de Javascript suministrándole la referencia al componente actual. Ya en el lado cliente, esa función podría emplear la referencia para invocar al método C# directamente o bien dejarla almacenada para usos posteriores:
@* File: Test.razor *@

@page "/test"
@inject IJSRuntime JsRuntime
...
@code {
DotNetObjectReference<Test> _reference;

async Task CreateAndSendReferenceAsync()
{
// Creamos la referencia al componente actual...
_reference = DotNetObjectReference.Create(this);

// ... y se la pasamos a una función Javascript
await JsRuntime.InvokeVoidAsync("interopDemo.initialize", _reference);
}
}
El envío de la referencia podemos hacerlo en el momento que más nos interese, que dependerá del escenario o funcionalidad que vayamos a implementar. Por ejemplo, podría realizarse justo al cargar la página para que la referencia se encuentre disponible en el browser en todo momento, o bien hacerlo cuando ocurra alguna condición determinada. A nuestro antojo.

Por ejemplo, si quisiéramos hacerlo cuando la página haya cargado totalmente, podríamos sobrescribir OnAfterRenderAsync() como se muestra a continuación:
@code {
DotNetObjectReference<Test> _reference;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await CreateAndSendReferenceAsync();
}
}

async Task CreateAndSendReferenceAsync() { ... } // Omitido por brevedad
}

3. Utilizar la referencia para invocar al método

Desde el código Javascript podríamos invocar muy fácilmente al método de la instancia referenciada ejecutando sobre ella invokeMethodAsync(), pasándole como parámetro el nombre del método de instancia a ejecutar.

El siguiente código script, que podría encontrarse dentro de un archivo .js incluido en la página, define un objeto llamado interopDemo en cuyo interior encontramos dos funciones. La primera de ella, initialize() es la que anteriormente hemos utilizado para enviar la referencia, que dejamos guardada en una variable local; la segunda, increment() es donde utilizamos dicha referencia para invocar al código C# suministrándole un parámetro:
window.interopDemo = (function () {
var reference = null;
return {
initialize: function (ref) { reference = ref; },
increment: function() {
reference.invokeMethodAsync("Increment", 1);
}
};
})();
El método de instancia Increment() debe encontrarse en el objeto apuntado por la referencia, ser público y estar decorado con [JSInvokable], como el que sigue a continuación:
@code {
int counter = 0;
... // Otros miembros omitidos

[JSInvokable]
public async Task Increment(int num)
{
counter += num;
StateHaschanges(); // Fuerza el renderizado si hay cambios
}
}
Un ejemplo de uso de la función Javascript increment() podría ser el siguiente HTML, donde vemos que introducimos la llamada en el evento onclick de un botón:
<h2>Interop demo</h2>
<button onclick="interopDemo.increment()">Increment</button>
<p>Counter: @counter</p>
Fijaos que, a diferencia de la página de ejemplo Counter.razor que encontramos en la plantilla por defecto, en este caso no estamos utilizando el sistema de bindings de Blazor para incrementar el contador: estamos usando Javascript puro en la página, e invocando a un método C# para realizar la operación y refrescar la interfaz.

4. Liberar la instancia

Es importante tener en cuenta que, para evitar fugas de memoria, esta referencia debe ser liberada llamando a su método Dispose() cuando deje de tener sentido.

Por ejemplo, si estamos en el contexto de un componente Blazor, un buen momento para hacerlo podría ser el propio Dispose() del componente. Para ello debemos hacerlo IDisposable usando la directiva @implements como en el siguiente ejemplo:
@page "/test"
@inject IJSRuntime JsRuntime
@implements IDisposable

...
@code {
DotNetObjectReference<MyClass> _reference;
int counter = 0;

// Omitidos por brevedad
protected override async Task OnAfterRenderAsync(bool firstRender) { ... }
async Task CreateAndSendReferenceAsync() { ... }
[JSInvokable] async Task Increment(int num) { ... }

public void Dispose()
{
_reference?.Dispose();
}
}
Como alternativa, se puede también liberar la referencia desde el lado Javascript, llamando al método dispose() de la misma.

El ejemplo completo

El siguiente bloque de código es el componente "Test.razor" que contiene la interfaz de usuario y el método Increment() que será consumido desde el lado cliente:
@page "/test"
@inject IJSRuntime JsRuntime
@implements IDisposable

<h1>Interop demo</h1>

<button onclick="interopDemo.increment()">Increment</button>
<p>Counter: @counter</p>

@code {
int counter = 0;
DotNetObjectReference<Test> _reference;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await CreateAndSendReferenceAsync();
}
}

async Task CreateAndSendReferenceAsync()
{
_reference = DotNetObjectReference.Create(this);
await JsRuntime.InvokeVoidAsync("interopDemo.initialize", _reference);
}

[JSInvokable]
public async Task Increment(int num)
{
counter += num;
StateHasChanged();
}

public void Dispose()
{
_reference?.Dispose();
}
}
La parte Javascript que realiza el registo de la referencia y la invocación al método ya la hemos visto anteriormente. Obviamente, este script debe encontrarse en la página o en algún archivo .js referenciado por ella:
window.interopDemo = (function () {
var reference = null;
return {
initialize: function (ref) { reference = ref; },
increment: function() {
reference.invokeMethodAsync("Increment", 1);
}
};
})();
Y con esto, creo que hemos terminado con la Blazor interop y, por tanto, esta mini-serie de tres capítulos en la que llevamos algunas semanas. Por supuesto, si véis que falta algo no dudéis en comentarlo :)

Publicado en Variable not found.

» Leer más, comentarios, etc...

Bitácora de Javier Gutiérrez Chamorro (Guti)

PowerArchiver en español

julio 06, 2020 09:50



No recuerdo en cuantos proyectos de traducción y/o localización de software he participado, pero muchos. Haciendo un repaso rápido, y además del propio FileOptimizer en español, recuerdo Avira Antivir del que además me encargaba de la parte del soporte a usuarios; CubicExplorer; Simple PHP Blog (SPHPBlog); GreatNews; … La traducción de software es un proceso …

PowerArchiver en español Leer más »



Artículo publicado originalmente en Bitácora de Javier Gutiérrez Chamorro (Guti)

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 411

julio 06, 2020 06:05

Enlaces interesantes

El código de estado HTTP 411, length required, es enviado al cliente para indicar que la petición no puede ser aceptada si no incluye el encabezado content-length indicando el tamaño en bytes del cuerpo de la misma.

Ahí van los enlaces recopilados durante la semana pasada, la última entrega antes de comenzar las vacaciones. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET Core / .NET

ASP.NET Core / ASP.NET

Azure / Cloud

    Data

    Machine learning / IA / Bots

    Web / HTML / CSS / Javascript

    Visual Studio / Complementos / Herramientas

    Xamarin

    Windows calculator

    Otros

    Publicado en: www.variablenotfound.com.

    » Leer más, comentarios, etc...

    Fixed Buffer

    ¡¡Renovado como MVP 2020!!

    julio 05, 2020 02:51

    Tiempo de lectura: 2 minutos
    MVP Award

    El pasado 1 de julio tuve la suerte de renovar como Microsoft MVP en la categoría ‘Developer Technologies’. Me hubiese gustado publicar algo en el mismo momento más allá de un mensaje en Twitter, pero la situación familiar no me lo ha permitido hasta ahora.

    Para mí es todo un honor que me hayan dado este reconocimiento por segunda vez consecutiva ya que me ha permitido conocer y colaborar con grandes profesionales, algunos convertido ya en amigos.

    ¿Cómo ha sido mi experiencia este año? Pues me encontraba yo hablando con Jose M. Aguilar de VariableNotFound mientras me explicaba las bondades de Blazor, cuando recibí el mail. Poco después de mí, Jose también recibió el mail (¡¡¡felicidades!!!) dando el cierre a un día muy interesante :).

    Como no podría ser de otra manera, el mérito no es solo mío. De hecho, creo que es más de mi novia que mío. Ella es la que me aguanta tantas horas delante del ordenador investigando, probando y documentando, además de escribiendo las entradas que finalmente acaban publicadas. Desde aquí gracias por estar día a día aguantando este estilo de vida de ordenador y charlas.

    ¿Y qué es de un blog sin lectores? Pues que no sería un blog… Es por eso que también quiero agradecer a todos los lectores que día a día haces que este pequeño rincón de internet que es FixedBuffer siga adelante. Este reconocimiento también os pertenece a todos y cada uno de vosotros que estáis ahí semana tras semana y mes tras mes.

    Ya para terminar, una mención especial para Cristina Herrero e Irene Otero, managers del programa MVP en la región por el trabajo que hacen y las facilidades que ofrecen para hacer crecer más y más a la comunidad.

    **La entrada ¡¡Renovado como MVP 2020!! se publicó primero en Fixed Buffer.**

    » Leer más, comentarios, etc...

    Variable not found

    ¡MVP*10!

    julio 02, 2020 06:18

    MVP*10Es una alegría, un honor y un auténtico lujo poder compartir con todos vosotros que, por décimo año consecutivo, he sido reconocido por Microsoft como Most Valuable Professional (MVP) en la categoría Developer technologies.

    Cuando fui nombrado MVP por primera vez, jamás pensé que este privilegio fuera a durar tanto. Un par de añitos quizás, lo suficiente como para poder visitar Redmond alguna vez y poder contar a los nietos que "yo estuve allí" ;) Pero diez años más tarde, aquí estamos todavía, con las mismas ganas y entusiasmo de poder seguir formando parte de este club de amigos a los que sigo y admiro tanto. ¡Impresionante!

    Esta vez la notificación me ha pillado en una videollamada con el gran Jorge Turrado, amigo y compañero MVP, que me ha sacado del fragor de la batalla diaria para inyectarme la dosis de adrenalina que supone darse cuenta de pronto de que hoy era el día de nombramientos y todavía no me había llegado el famoso email :D Por cierto, Jorge ¡felicidades, por tu merecida renovación!

    Muchas gracias a todos los que hacéis posible que me lleve estos momentazos. A los amigos y amigas de blog, porque sin su apoyo no habría sido posible llegar hasta tan lejos; al equipo del programa MVP, por su incansable labor y exquisito trato con todos, y, por supuesto, a mis tres niñas, por consentírmelo todo :)

    Finalmente, no me gustaría cerrar este post sin enviar mi más sincera enhorabuena a los nuevos MVP; disfrutad de ese momento tan bonito. Si renováis en el futuro os llevaréis grandes alegrías, pero la primera vez nunca se olvida. Y también enviar un fuerte abrazo (con distancia social, eso sí ;)) a los que repetís galardón: como ya he dicho alguna vez, lo difícil no es sólo llegar, sino también mantenerse.

    Publicado en Variable not found.

    » Leer más, comentarios, etc...

    Picando Código

    Actualización a Montevideo Bicis con datos de accidentes 2019

    junio 30, 2020 12:00

    Montevideo Bicis es un sitio web que presenta información “objetiva” para gente que quiera circular en bicicleta en Montevideo. Aprovecha Datos Abiertos de la Intendencia de Montevideo y y UNASEV. Recientemente UNASEV liberó los datos de accidentes de tránsito de 2019, así que el sitio está actualizado con los datos de accidentes de tránsito hasta 2019.

    También corregí un error en el JavaScript del comportamiento de los botones de “Datos completos”. Ahora se muestran bien las tablas con los datos completos de todos los años. Hay información desde 2012 hasta 2019, y mientras sigan abriendo los datos, seguiré actualizando.

    Aproveché la actualización también para hacer algunas mejoras en el código, más que nada en cuanto a legibilidad para que me sea más fácil de mantener. En algún momento hasta le dedicaré un tiempo más para eliminar todo lo que va quedando del “Hackathon Driven Development” que le dio vida.

    Estaciones Movete

    El sitio incluye un mapa con información también de datos abiertos de la Intendencia de Montevideo. Uno de los datos son las estaciones Movete, estaciones de préstamo de bicicletas públicas en Montevideo. Lamentablemente me enteré que este servicio dejó de funcionar. En parte lo atribuyen a la reducción importante de usuarios, que yo atribuiría a la falta de infraestructura y educación vial contra la cual pretende luchar el sitio. Pero no entremos en una verborragia violenta que bastantes problemas tiene el mundo hoy para andar enojándose porque una ciudad no es amigable para los ciclistas…

    Los datos del mapa están basados en datos de la Intendencia de Montevideo, pero según la fuente no se actualizan desde marzo de 2018. Tengo entendido que se han creado varias ciclovías nuevas desde entonces, pero necesito que se actualicen los datos fuente. Ya agregué a mano una vez el estacionamiento del MNAV (¡gracias Eduardo de Informática en MNAV!), pero no tengo información (y paciencia) para agregar a mano las rutas.

    En fin, si les interesa el tema, pueden visitar MontevideoBicis.com, y ver el código fuente en GitHub. Y si tienen ideas o críticas sobre el sitio, son más que bienvenidos a compartirlos en los comentarios de este post.

     

    Montevideo Bicis 2019

    » Leer más, comentarios, etc...

    Fixed Buffer

    Contenedores Docker en Azure (aci) desde Docker Desktop

    junio 30, 2020 08:00

    Tiempo de lectura: 8 minutos
    Imagen ornamental con el logo de Azure Container Instances (aci) para la entrada sobre como desplegar contenedores en Azure con Docker Desktop

    Se van acercando las vacaciones de verano y ya están tan cerca que se pueden sentir. Con las vacaciones de verano llega el final de la temporada y un merecido descanso. Con esa idea en mente, pensaba cerrar la temporada con una entrada sobre Dapper que se iba a publicar en lugar de esta, pero el reciente anuncio de Microsoft sobre el soporte experimental para desplegar contenedores Docker directamente en Azure Container Instances (aci de aquí en adelante) desde Docker Desktop ha trastocado un poco los planes.

    Si quieres saber como desplegar un agente de DevOps efímero sobre aci, no te pierdas la entrada ‘Azure DevOps Ephimeral Agents: Agentes de usar y tirar

    ¿Por qué es tan interesante poder desplegar aci directamente desde Docker Desktop?

    Normalmente, trabajar con Docker en local es más que suficiente para poder hacer pruebas y desarrollos que antes o después acaben desplegados por ejemplo en Kubernetes. Aunque este suele ser el proceso habitual, también existen modelos de orquestación basados en docker-compose.

    Imagina que necesitas que un equipo de testing haga pruebas sobre una web que estas desarrollando. Si bien es cierto que se podría desplegar la imagen sobre un App Service directamente, habría que gestionarlo a parte, actualizar sus imágenes,…

    Gracias a este nuevo modelo, vamos a poder tener un funcionamiento mucho más sencillo, ya que va a ser directamente desde Docker Desktop donde vamos a gestionar los despliegues a contenedores en Azure Container Instances. Nuestro flujo de trabajo sería algo como esto:

    La imagen muestra un diagrama de flujo hacia DockerHub/ACR y desde ahí hacia Azure Container Instances

    Aunque ahora mismo está en una fase muy preliminar (se hizo público el 25 de junio de 2020), la idea detras de esto es conectar el aci con por ejemplo un Storage de Azure de modo que tengamos volúmenes persistentes o que podamos por ejemplo crear un despliegue complejo utilizando directamente docker-compose.

    Esto se suma a que los recursos dejan de ser parte de nuestra máquina para empezar a ser parte de la infraestructura de Azure. De este modo, si nuestro entorno tiene un consumo que nuestra máquina no puede soportar bien, al estar alojado en Azure ese problema desaparece de raíz.

    Configurando Docker Desktop para desplegar sobre ACI

    Llegados a este punto, vamos a empezar a configurar Docker Desktop para poder desplegar sobre ACI. El primero de los requisitos es tener la versión 2.3.2.0 Edge (o superior). Esta versión se puede descargar desde le propia web de descargas de Docker Desktop:

    La imagen señala el canal Edge de MacOS y de Windows en la sección de descargas de Docker Desktop

    En caso de que todavía no este disponible la versión 2.3.2.0 en las descargas (a mí me ha pasado), puedes descargarlo utilizando uno de estos enlaces para Windows o para MacOS.

    Una vez que hemos instalado la nueva versión, lo primero que vamos a necesitar es autenticarnos con nuestra cuenta de Azure. Para esto basta con ejecutar el comando:

    docker login azure
    

    Esto nos enviará a la web de Azure para que introduzcamos las credenciales. Una vez que nos autentiquemos, en la consola recibiremos un mensaje de confirmación de que todo ha ido bien:

    PS C:\Users\jorge> docker login azure
    login succeeded
    

    Con esto, ya casi tenemos todo listo para desplegar contenedores en Azure (aci) desde Docker Desktop. Solo nos falta un paso más, crear un contexto de tipo aci en Docker Desktop. Para esto basta con ejecutar:

    docker context create aci myacicontext
    

    Este comando nos permite a día de hoy, definir ciertos parámetros adicionales para poder ajustar más la configuración. Estos parámetros son:

    • location: Permite definir la ubicación de los contenedores
    • resource-group: Permite indicar que los contenedores deben crearse en un grupo de recursos concreto (que debe existir previamente)
    • subscription-id: Permite indicar el Id de una suscripción en concreto de Azure, especialmente útil si tienes más de una suscripción en tu cuenta.

    Por ejemplo, en caso de definir los 3 parámetros el comando sería algo así:

    docker context create aci myacicontext --location westeurope --subscription-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --resource-group docker-aci
    

    Con esto, ya tenemos disponible el contexto myacicontext listo para desplegar contenedores aci desde Docker Desktop. Esto podemos comprobarlo con el comando:

    docker context ls
    

    Como mínimo, este comando debería respondernos 2 contextos, el default de Docker Desktop y el que acabamos de crear.

    PS C:\Users\jorge> docker context ls
    NAME                TYPE                DESCRIPTION                               DOCKER ENDPOINT                  KUBERNETES ENDPOINT   ORCHESTRATOR
    default             moby                Current DOCKER_HOST based configuration   npipe:////./pipe/docker_engine                         swarm
    myacicontext *      aci                 docker-aci@westeurope
    

    De ahora en adelante, podemos especificar sobre que contexto queremos aplicar los comandos Docker con el argumento –context Contexto. También podemos especificar el contexto por defecto que debe utilizarse si no se informa específicamente con el comando:

    docker context use contexto
    

    Vamos a probarlo

    Una vez que tenemos el contexto listo, basta con ejecutar un run normal para que se despliegue automáticamente. Por ejemplo, podemos utilizar el comando:

    docker run -p 80:80 -d nginx
    

    Esto mostrará por consola una salida un tanto diferente a la habitual cuando se usa el contexto local:

    PS C:\Users\jorge> docker run -p 80:80 -d nginx
    [+] Running 2/2
     - sleepy-cray             Created                                         4.5s
     - single--container--aci  Done                                           20.6s
    sleepy-cray
    

    Si ahora vamos a la suscripción de Azure en el grupo de recursos que le hemos indicado, podemos encontrarnos algo como esto:

    La imagen muestra el grupo de recursos docker-aci de con un aci llamado sleepy-cray

    En caso de que cuando creásemos el contexto no le indicásemos un grupo de recursos, se crearía uno automáticamente con un GUID como nombre.

    Podemos gestionar el propio contenedor desde el mismo Docker. En este caso hemos desplegado una imagen de nginx y hemos mapeado el puerto 80, por tanto, si obtenemos la IP del contendor, deberíamos poder acceder desde cualquier navegador. Para obtener esa IP basta con que ejecutemos:

    docker ps
    

    Esto nos mostrará por consola la salida habitual del comando, indicándonos la IP del contenedor:

    CONTAINER ID        IMAGE               COMMAND             STATUS              PORTS
    sleepy-cray         nginx                                   Running             52.143.11.203:80->80/tcp
    

    Basta con navegar a la dirección IP para ver que todo funciona correctamente:

    La imagen muestra la página de bienvenida de nginx y señala la IP desde la que se ha accedido que es 52.143.11.203

    Al igual que hacemos con cualquier otro contenedor, también podríamos obtener los logs desde el aci utilizando en Docker Desktop el comando:

    docker logs id-del-contenedor
    

    Por ejemplo, para nuestro nginx:

    PS C:\Users\jorge> docker logs sleepy-cray
    /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
    /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
    /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
    10-listen-on-ipv6-by-default.sh: Getting the checksum of /etc/nginx/conf.d/default.conf
    10-listen-on-ipv6-by-default.sh: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
    /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
    /docker-entrypoint.sh: Configuration complete; ready for start up
    10.240.255.56 - - [28/Jun/2020:19:37:17 +0000] "GET / HTTP/1.1" 400 157 "-" "-" "-"
    10.240.255.55 - - [28/Jun/2020:19:43:13 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.56" "-"
    10.240.255.55 - - [28/Jun/2020:19:43:13 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://52.143.11.203/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.56" "-"
    2020/06/28 19:43:13 [error] 27#27: *154 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 10.240.255.55, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "52.143.11.203", referrer: "http://52.143.11.203/"
    PS C:\Users\jorge>
    

    Por último, para borrar el contenedor basta con que ejecutemos el comando:

    docker rm id-del-contenedor
    

    Utilizando Docker Compose sobre aci

    Hasta ahora hemos planteado el uso de docker para levantar contenedores individuales, pero no es la única posibilidad. Gracias a Docker Compose podemos orquestar diferentes configuraciones de contenedores de manera que podamos por ejemplo levantar una web y una base de datos. Como no podía ser de otra manera, esta es una característica soportada por esta versión preview (aunque con muchas limitaciones).

    Para poder usar esta funcionalidad, basta con que creemos un fichero docker-compose.yml con un contenido como este:

    version: '3.4'
    
    services:
       db:
         image: mysql:5.7
         restart: always
         environment:
           MYSQL_ROOT_PASSWORD: somewordpress
           MYSQL_DATABASE: wordpress
           MYSQL_USER: wordpress
           MYSQL_PASSWORD: wordpress
    
       wordpress:
         depends_on:
           - db
         image: wordpress:latest
         ports:
           - "80:80"
         restart: always
         environment:
           WORDPRESS_DB_HOST: db:3306
           WORDPRESS_DB_USER: wordpress
           WORDPRESS_DB_PASSWORD: wordpress
           WORDPRESS_DB_NAME: wordpress
    

    Una vez hecho el fichero, basta con ejecutar el comando:

    docker compose -f .\docker-compose.yml up
    

    OJO. El comando es docker compose y no docker-compose. Esto es porque se utiliza la docker cli en vez de docker-compose cli.

    Tras la ejecución del comando, podremos obtener una salida como esta:

    PS C:\Users\jorge\OneDrive\Escritorio\aci> docker compose -f .\docker-compose.yml up  
    [+] Running 4/4
     - aci                Created                                  4.4s
     - db                 Done                                    66.7s
     - wordpress          Done                                    66.7s
     - aci--dns--sidecar  Done                                    66.7s
    

    Esto va a levantar un aci con varios contenedores dentro para la base de datos, el propio wordpress y un contenedor adicional que sirve de dns entre ambos. Ahora mismo, podemos ir a la interfaz de Azure y ver que efectivamente se han creado:

    La imagen muestra la interfaz de Azure donde se ve centro del aci que se han desplegado los contenedores db, wordpress y aci--dns--sidecar

    Con esto deberíamos poder entrar en la IP que se ha asignado y usar nuestro wordpress, pero actualmente parece que o no está soportado el uso de variables o tiene algún bug por lo que nuestro escenario no es capaz de arrancar al no asignar las variables de entorno en el contenedor. Puedes seguir la información al respecto en esta incidencia.

    Conclusión

    Gracias a esta nueva vuelta de tuerca de la mano de Docker y Microsoft, se amplían mucho los posibles escenarios de desarrollo que podemos conseguir con Docker antes de llegar a desplegar en un Kubernetes. Es posible hacer despliegues de entornos de pruebas de manera muy sencilla y gestionarlo tal cual haríamos como si el entorno fuese local de nuestra máquina.

    Hay que reconocer que la línea actual de desarrollo es muy interesante en cuanto a sus ambiciones, despliegue de aci desde Docker Desktop directamente, pudiendo utilizar cuentas de almacenamiento de Azure como volúmenes para los contenedores o pudiendo desplegar con docker-compose como sistema de orquestación.

    Llegados a este punto, es también muy importante señalar que es un producto extremadamente reciente, que no está recomendado para su uso en producción y que su documentación actualmente brilla por su ausencia. Si bien es cierto que se detalla cómo crear un contenedor, no hay nada de información sobre cómo utilizar los volúmenes, o solución de errores.

    Aun así y con todo, pienso que apunta muchas maneras para tener su hueco en el día a día del desarrollo con Docker una vez que madure un poco.

    **La entrada Contenedores Docker en Azure (aci) desde Docker Desktop se publicó primero en Fixed Buffer.**

    » Leer más, comentarios, etc...

    Variable not found

    Cómo solucionar el error "Unable to connect to web server 'IIS Express'" en Visual Studio

    junio 30, 2020 07:51

    Unable to connect to web server 'IIS Express'

    Va un post rapidito, pero que seguro que puede ahorrar quebraderos de cabeza a más de uno que se encuentre con este problema al iniciar desde Visual Studio una aplicación ASP.NET, ASP.NET Core MVC/Web API, Razor Pages, o incluso Blazor que utilicen por debajo IIS Express.

    El problema ocurre justo al ejecutar la aplicación con F5 o Ctrl+F5 desde el entorno de desarrollo; en ese momento, la ejecución se detiene y aparece un cuadro de diálogo con el mensaje:

    "Unable to connect to web server 'IIS Express'"

    Creo que llevo años encontrándome de vez en cuando con este problema al arrancar las aplicaciones, y nunca entendí muy bien por qué pasaba. Buscaba por la red y solo encontraba soluciones relativas a eliminar el archivo de configuración applicationhost.config que Visual Studio guarda en la carpeta ".vs" de la solución, a reiniciar el IDE o incluso la máquina, abrirlo como Administrador, o cambiar el puerto en la configuración del proyecto, normalmente algunas unidades por arriba o por abajo que el puerto asignado inicialmente.

    Esta última opción es la que más veces me ha funcionado, pero no siempre iba bien, por lo que al final tampoco la identifiqué como una clara receta para paliar el problema que nos ocupa.

    Hace unos días me ha vuelto a ocurrir con un proyecto que uso muy a menudo, y, de un día para otro, ha dejado de funcionar y ha comenzado a lanzarme a la cara el maldito cuadro de diálogo. La diferencia es que por fin he podido encontrar una respuesta satisfactoria, al menos para alguno de los escenarios que pueden causar el error :)

    El motivo de este error es que Windows ha reservado para su uso interno el puerto utilizado por la aplicación, por lo que no podemos utilizarlo con IIS Express. La solución es sencilla: debemos cambiarlo por otro que esté libre.

    Y claro, la cuestión es saber qué puertos están libres en el equipo.

    Para ello, podemos ejecutar la siguiente instrucción de la consola, que nos mostrará justamente lo contrario, es decir, los puertos reservados:

    C:\>netsh interface ipv4 show excludedportrange protocol=tcp

    Protocolo tcp Intervalos de exclusión de puertos

    Puerto de inicio Puerto final
    ---------- --------
    2869 2869
    4800 4800
    5357 5357
    8145 8145
    9009 9009
    50000 50059 *
    54950 55049
    55050 55149
    55454 55553
    55554 55653
    61566 61665
    61666 61765
    61766 61865
    61866 61965
    63053 63152
    63153 63252
    63353 63452
    63453 63552
    63858 63957
    63958 64057
    64058 64157
    64158 64257

    * - Exclusiones de puertos administrados.

    C:\>_

    Lo que vemos en el listado son rangos de puertos. Por ejemplo, podemos ver que están ocupados desde el 50000 hasta el 50059, o desde el 64158 hasta el 64257. Si nuestro proyecto usa uno de ellos, se generará el maldito error (que, dicho sea de paso, ya podría dar algo más de información sobre lo que ocurre...)

    Sabiendo esto, ya es fácil localizar un puerto libre. En mi caso podemos ver que el 51001 está libre, así que solo hay que modificarlo en las propiedades del proyecto:

    Cambiar el puerto del proyecto

    ¡Espero que os sea de ayuda!

    Publicado en Variable not found.

    » Leer más, comentarios, etc...

    Variable not found

    Enlaces interesantes 410

    junio 29, 2020 06:05

    Enlaces interesantes

    Como ya adelantamos en la entrega 404 de enlaces interesantes, los códigos de estado 404 y 410 son muy similares, pues ambos permiten al servidor expresar que el recurso solicitado no existe.

    La diferencia entre ambos es que HTTP 410 (Gone) que el recurso existió pero ya no está disponible, mientras que HTTP 404 no permite distinguir entre un recurso que jamás ha existido y uno que simplemente ha desaparecido.

    Y ahora vamos con una nueva recopilación de enlaces que, como siempre, espero que os resulten interesantes. :-)

    Por si te lo perdiste...

    .NET Core / .NET

    ASP.NET Core / ASP.NET

    Azure / Cloud

    Conceptos / Patrones / Buenas prácticas

    Data

      Web / HTML / CSS / Javascript

      Visual Studio / Complementos / Herramientas

      Xamarin / MAUI

      Publicado en Variable not found.

      » Leer más, comentarios, etc...

      Bitácora de Javier Gutiérrez Chamorro (Guti)

      Feed en tiempo real del Instagram de @Guardatiempo

      junio 26, 2020 09:19



      Cuando en publiqué Fotos de relojes en el Instagram de Guardatiempo en el que hacía referencia a la cuenta de Instagram @guardatiempo, dejé pendiente un feed en tiempo real de las últimas publicaciones. Una forma en que no hiciera falta acudir a la red social para visualizar las últimas fotografías… ¡Y finalmente, aquí lo tenéis!

      Artículo publicado originalmente en Bitácora de Javier Gutiérrez Chamorro (Guti)

      » Leer más, comentarios, etc...

      Picando Código

      Ex-Zodiac – Shooter 3D con gráficos poligonales inspirado en los clásicos de los 90’s ahora en Kickstarter

      junio 24, 2020 11:00

      Hace un tiempo escribía en el blog sobre Ex-Zodiac, un juego inspirado en Star Fox original escrito en Godot. Desde entonces he ido siguiendo su desarrollo y estoy muy contento de ver que hay un plan para completar su desarrollo y financiarlo a través de Kickstarter.

      Ex-Zodiac

      Ex-Zodiac es un juego shooter con gráficos estilizados que evocan juegos 3D de principios de los 90’s. La protagonista Kyuu lucha para liberar los mundos del Sistema Estelar Sanzaru, invadido por la organización terrorista intergaláctica conocida como Zodiac. Ya hay un demo disponible que podemos descargar en Steam, y en lo que va de desarrollo se puede ver el potencial y lo divertido que viene siendo el juego.

      Características generales:

      – Estilo visual retro, colorido y con pocos polígonos emulando los juegos de la época.
      – 12 niveles principales (más áreas secretas y caminos alternativos).
      – Varias rutas para completar el juego.
      – Jefes gigantes al final de cada nivel, cada uno pilotado por un miembro de Zodiac.
      – Banda sonora estilo 16-bit por +TEK combinando FM y síntesis wavetable.

      Su desarrollador es Ben Hickling quien viene trabajando en él hace más de 2 años en su tiempo libre. El objetivo del Kickstarter es que pueda dedicarle más tiempo al desarrollo y terminarlo. Va a estar disponible inicialmente en Linux, Mac y Windows por medio de Steam, Humble e Itch.io. Dentro de las recompensas del Kickstarter además de obtener el juego, podemos obtener el soundtrack por medio de Steam o Bandcamp, y un montón de extras más.

      Pixeljam va a estar ayudando con el marketing, community management y asegurarse que la campaña en Kickstarter cumpla con sus promesas. La empresa ha creado, consultado en y publicado casi 30 juegos desde 2005, siendo Dino Run y Nova Drift sus títulos más conocidos.

      La campaña recién empezó y ya alcanzó la meta inicial de USD 25.190. Inicialmente no hay “stretch goals” planeados, con la idea de que todo el dinero recaudado por encima de la meta inicial va a ser volcado en “más juego”. Tampoco hay muchas recompensas extra, todo el contenido es digital, y el desarrollador se puede concentrar simplemente en desarrollar y terminar el juego, y no preocuparse por producir y entregar recompensas físicas. Tras haber sido quemado más de una vez por Kickstarters que prometen demasiado y terminan sin entregar nada o un producto mediocre, me parece excelente mantener expectativas y metas medidas para poder cumplirlas.

      Una buena noticia es que está en la mira la posibilidad de portar el juego a Nintendo Switch. Como se encuentra en una etapa muy temprana de desarrollo, no prometen nada. Pero una vez publicado el juego, van a tener una mejor idea de lo que se necesita para publicarlo en Switch. Cuentan con la experiencia y recursos de Pixeljam para esto.

      Visita el Kickstarter

      YouTube Video

      » Leer más, comentarios, etc...

      Bitácora de Javier Gutiérrez Chamorro (Guti)

      Casio necesita un buen software y el desastre de G-Shock Connected

      junio 21, 2020 04:14



      En el artículo ¿El futuro de los relojes digitales? ya referenciaba las deficiencias del software G-Shock Connected escrito por Casio Computer Co. Ltd. Un aspecto que ya había mencionado en las pruebas de los Casio G-Shock GMW-B5000D, GW-B5600 y sobre todo del GPR-B1000 «Rangeman». No importa si hablamos de plataforma iOS como Android, en ambas …

      Casio necesita un buen software y el desastre de G-Shock Connected Leer más »



      Artículo publicado originalmente en Bitácora de Javier Gutiérrez Chamorro (Guti)

      » Leer más, comentarios, etc...

      Meta-Info

      ¿Que es?

      Planeta Código es un agregador de weblogs sobre programación y desarrollo en castellano. Si eres lector te permite seguirlos de modo cómodo en esta misma página o mediante el fichero de subscripción.

      rss subscripción

      Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
      planetacodigo

      planetacodigo

      Si tienes un weblog de programación y quieres ser añadido aquí, envíame un email solicitándolo.

      Idea: Juanjo Navarro

      Diseño: Albin