ICP-Brasil client certificates and Nginx

Nginx support client certificates, that means Nginx can ask for a certificate to the client browser. The user certificate is checked against the CA certificate configured in Nginx service.

In Brazil, the root authority signing certificates is called ICP-Brasil. There is an intermediate signer, called Instituto Nacional de Tecnologia da Informação (ITI).

All brazilians people and business can buy an always expensive certificate to get access to brazilian government services, to pay abusive taxes and report annual income.

It is possible to use the CA certificates provided by ITI to authenticate users holding brazilian certificates.

Getting CA certs from ICP-Brazil

# Download the current certificates
curl http://acraiz.icpbrasil.gov.br/credenciadas/CertificadosAC-ICP-Brasil/ACcompactado.zip -o /tmp/ACcompactado.zip

# Create a folder to decompress the certs
mkdir /etc/ssl/icp-brasil

# Decompress the files
unzip -d /etc/ssl/icp-brasil /tmp/ACcompactado.zip

# Remove the zip file
rm /tmp/ACcompactado.zip

Picking correct CA files to Nginx

As of April 2021, 3 files from the downloaded zip are needed to work with Nginx:

  1. /etc/ssl/certs/icp-brasil/AC_Certisign_RFB_G5.crt
  2. /etc/ssl/certs/icp-brasil/AC_Secretaria_da_Receita_Federal_do_Brasil_v4.crt
  3. /etc/ssl/certs/icp-brasil/ICP-Brasilv5.crt

You need to concatenate the 3 files above into a single one Nginx will read. Something like below

{ \
    cat /etc/ssl/certs/icp-brasil/AC_Certisign_RFB_G5.crt; \
    cat /etc/ssl/certs/icp-brasil/AC_Secretaria_da_Receita_Federal_do_Brasil_v4.crt; \
    cat /etc/ssl/certs/icp-brasil/ICP-Brasilv5.crt; \
} > /etc/ssl/certs/icp-brasil/ICP-Brasil-bundle.crt

Setting up Nginx

The 3 directives below are needed by Nginx to start requesting the client certificate from ICP-Brasil certificate holders.

ssl_client_certificate /etc/ssl/certs/icp-brasil/ICP-Brasil-bundle.crt;
ssl_verify_client on;
ssl_verify_depth 2;

Nginx has many variables containing information about the matched certificate. The proxied application probably needs to have access on these variable in order to authenticate and/or register the user. This information can be forwarded to the application like below.

        fastcgi_param SSL_CIPHER              $ssl_cipher              if_not_empty;
        fastcgi_param SSL_CIPHERS             $ssl_ciphers             if_not_empty;
        fastcgi_param SSL_CLIENT_CERT         $ssl_client_cert         if_not_empty;
        fastcgi_param SSL_CLIENT_ESCAPED_CERT $ssl_client_escaped_cert if_not_empty;
        fastcgi_param SSL_CLIENT_FINGERPRINT  $ssl_client_fingerprint  if_not_empty;
        fastcgi_param SSL_CLIENT_I_DN         $ssl_client_i_dn         if_not_empty;
        fastcgi_param SSL_CLIENT_I_DN_LEGACY  $ssl_client_i_dn_legacy  if_not_empty;
        fastcgi_param SSL_CLIENT_RAW_CERT     $ssl_client_raw_cert     if_not_empty;
        fastcgi_param SSL_CLIENT_S_DN         $ssl_client_s_dn         if_not_empty;
        fastcgi_param SSL_CLIENT_S_DN_LEGACY  $ssl_client_s_dn_legacy  if_not_empty;
        fastcgi_param SSL_CLIENT_SERIAL       $ssl_client_serial       if_not_empty;
        fastcgi_param SSL_CLIENT_V_END        $ssl_client_v_end        if_not_empty;
        fastcgi_param SSL_CLIENT_VERIFY       $ssl_client_verify       if_not_empty;
        fastcgi_param SSL_CLIENT_V_REMAIN     $ssl_client_v_remain     if_not_empty;
        fastcgi_param SSL_CLIENT_V_START      $ssl_client_v_start      if_not_empty;
        fastcgi_param SSL_CURVES              $ssl_curves              if_not_empty;
        fastcgi_param SSL_EARLY_DATA          $ssl_early_data          if_not_empty;
        fastcgi_param SSL_PROTOCOL            $ssl_protocol            if_not_empty;
        fastcgi_param SSL_SERVER_NAME         $ssl_server_name         if_not_empty;
        fastcgi_param SSL_SESSION_ID          $ssl_session_id          if_not_empty;
        fastcgi_param SSL_SESSION_REUSED      $ssl_session_reused      if_not_empty;

Sample Nginx config

server {
    listen 80;
    listen 443 ssl;
    server_name testcert.montefuscolo.com.br;

    root /var/www/html;
    access_log /var/log/nginx/testcert.montefuscolo.com.br_access_log;
    error_log /var/log/nginx/testcert.montefuscolo.com.br_error_log;

    ssl_certificate /etc/letsencrypt/live/testcert.montefuscolo.com.br/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/testcert.montefuscolo.com.br/privkey.pem;

    ssl_client_certificate /etc/ssl/certs/icp-brasil/ICP-Brasil-bundle.crt;
    ssl_verify_client on;
    ssl_verify_depth 2;

    index index.html index.htm index.php;

    location ~ \.php(/|$) {
        include fastcgi.conf;

        fastcgi_param SSL_CIPHER              $ssl_cipher              if_not_empty;
        fastcgi_param SSL_CIPHERS             $ssl_ciphers             if_not_empty;
        fastcgi_param SSL_CLIENT_CERT         $ssl_client_cert         if_not_empty;
        fastcgi_param SSL_CLIENT_ESCAPED_CERT $ssl_client_escaped_cert if_not_empty;
        fastcgi_param SSL_CLIENT_FINGERPRINT  $ssl_client_fingerprint  if_not_empty;
        fastcgi_param SSL_CLIENT_I_DN         $ssl_client_i_dn         if_not_empty;
        fastcgi_param SSL_CLIENT_I_DN_LEGACY  $ssl_client_i_dn_legacy  if_not_empty;
        fastcgi_param SSL_CLIENT_RAW_CERT     $ssl_client_raw_cert     if_not_empty;
        fastcgi_param SSL_CLIENT_S_DN         $ssl_client_s_dn         if_not_empty;
        fastcgi_param SSL_CLIENT_S_DN_LEGACY  $ssl_client_s_dn_legacy  if_not_empty;
        fastcgi_param SSL_CLIENT_SERIAL       $ssl_client_serial       if_not_empty;
        fastcgi_param SSL_CLIENT_V_END        $ssl_client_v_end        if_not_empty;
        fastcgi_param SSL_CLIENT_VERIFY       $ssl_client_verify       if_not_empty;
        fastcgi_param SSL_CLIENT_V_REMAIN     $ssl_client_v_remain     if_not_empty;
        fastcgi_param SSL_CLIENT_V_START      $ssl_client_v_start      if_not_empty;
        fastcgi_param SSL_CURVES              $ssl_curves              if_not_empty;
        fastcgi_param SSL_EARLY_DATA          $ssl_early_data          if_not_empty;
        fastcgi_param SSL_PROTOCOL            $ssl_protocol            if_not_empty;
        fastcgi_param SSL_SERVER_NAME         $ssl_server_name         if_not_empty;
        fastcgi_param SSL_SESSION_ID          $ssl_session_id          if_not_empty;
        fastcgi_param SSL_SESSION_REUSED      $ssl_session_reused      if_not_empty;

        fastcgi_pass localhost:4567;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
    }
}

References

  • http://nginx.org/en/docs/http/ngx_http_ssl_module.html#variables
  • https://gist.github.com/skarllot/9663935
  • https://github.com/c0h1b4/autenticacao-ICP-Brasil/blob/master/NGINX/site.nginx
  • https://stackoverflow.com/questions/8431528/nginx-ssl-certificate-authentication-signed-by-intermediate-ca-chain
Categories
Software Development

Fix the PHP timezone

Some applications need to know the correct timezone to perform some specific actions and may be a good idea set the date.timezone[1] on PHP configuration to have predictable results in these applications.

If the command below doesn’t give any output, probably the date.timezone is unset.

$ php -r "echo ini_get('date.timezone');"
>>>

It is possible to check the date string will be outputted from application with the function date[2] and the current set timezone, using the command below.

$ php -r "echo date('r');"
>>> Fri, 22 Nov 2019 14:28:03 +0000

The +0000 indicates the current timezone is UTC, the PHP default when date.timezone is not set.

After picking a timezone option[3] from PHP documentation, the test to check if that option will give the expected result can be made with the command below, supposing the chosen option is America/Sao_Paulo.

$ php -d date.timezone=America/Sao_Paulo -r "echo date('r');"
>>> Fri, 22 Nov 2019 11:35:38 -0300

The -0300 indicates the America/Sao_Paulo timezone. And to make it persistent, the date.timezone should be set in php.ini. That place of this file may vary from distro to distro.

In Archlinux, all files with the extension .ini dropped in the folder /etc/php/conf.d will be loaded as extensions of php.ini. The command below add the timezone to PHP configuration.

# echo date.timezone=America/Sao_Paulo >> /etc/php/conf.d/my-customs.ini

After that it is possible to run the same commands above to get the date string using the chosen timezone.

$ php -r "echo ini_get('date.timezone');"
>>> America/Sao_Paulo

$ php -r "echo date('r');"
>>> Fri, 22 Nov 2019 11:51:25 -0300

References

  1. https://www.php.net/manual/en/datetime.configuration.php#ini.date.timezone
  2. https://www.php.net/manual/en/function.date.php
  3. https://www.php.net/manual/en/timezones.php
Categories
Software Development

Change WordPress uploads URL

In some occasions may be necessary that file uploads URL points to somewhere else different of standard place. This can be done by using the option upload_url_path.

To change the path http://example.com/wp-content/uploads and let it like http://static.example.com/wp-content/uploads, the option upload_url_path should be set like below.

update_option(
    'upload_url_path',
    'http://static.example.com/wp-content/uploads'
);

Reference

  • https://developer.wordpress.org/reference/functions/wp_upload_dir/
Categories
Software Development

WordPress development with Docker Compose

Last post I told you about how to setup two dockers to run WordPress and MariaDB together. So, in this post I will show thow to use the docker-compose, that is a tool to help you setup run your dockers with one single command.

Installing

In cool distros you should be able to install docker-compose with command below.

sudo pacman -S docker-compose

In other distros, you should have the pip installed to have docker-compose. With pip, try the command below

sudo pip install docker-compose

Setup

The docker-compose utility rely on a file named docker-compose.yml. This file stores information about how to run one or more docker containers. You can quickly start, stop and remove a set of dockers with docker-compose.

For example, you can have a docker-compose.yml at root of your theme project and with a single command, start a set of dockers to start wordpress and mysql with your plugin already installed in plugins folder. Check the docker-compose.yml below.

web:
  image: montefuscolo/wordpress
  volumes:
   - ./:/var/www/html/wp-content/plugins/mapasdevista
  environment:
   - WORDPRESS_WP_DEBUG=true
  links:
   - db:mysql
  ports:
   - "80:80"
db:
  image: mariadb
  environment:
   - MYSQL_USER=thewpuser
   - MYSQL_PASSWORD=thewppass
   - MYSQL_DATABASE=wordpress
   - MYSQL_ROOT_PASSWORD=thesuperpass
  1. web: and db: There are 2 dockers declared, one named web and other named db
  2. image: A custom WordPress image[1] is used for web container
  3. volumes: The local folder is flagged to be mounted inside plugins folder of web container
  4. environment: a variable is declared to turn on debug in WordPress
  5. links: db is linked with web, but inside web container the db is called mysql
  6. ports: the port 80 is exposed and published from web to host
  7. In db container, the official maridb image is used

With this file placed at your theme folder, anyone with docker-compose can quickly setup a environment for test and develop the plugin.

Using

All commands should be run at same folder of docker-compose.yml.

To create and run containers try the command below and point your browser to http://localhost.

$ docker-compose up

To stop containers you can type Ctrl+C at terminal running docker-compose or in another terminal in same folder, you can use the command below.

$ docker-compose stop

To start your already created containers, the command is:

$ docker-compose start

To stop and destroy your containers:

$ docker-compose down

Conclusion

Using docker-compose you can run and develop your WordPress stuff right after clone. You don’t need to worry about setup a webserver, database, setup permissions. With docker you can get all running quickly.

References

  1. https://hub.docker.com/r/montefuscolo/wordpress/~/dockerfile/
  2. https://github.com/hacklabr/mapasdevista/blob/master/docker-compose.yml
  3. https://docs.docker.com/compose/reference/
Categories
Software Development

WordPress development with Docker

I started to learn about Docker[1] and how to use it. Docker let you run an application inside a container. This container pretends to be an operating system, but it uses the kernel shared by the host running docker. It’s a kind of light virtualization.

Here at Hacklab, we start our WordPress projects by copying the WordPress source code to a git repository and after, we create our themes and plugins. It’s horrible, I know, but everybody here knows how to do their jobs if we play in this way.

But now, with Docker, I can imagine a different way to work. I did a test forking[2] official WordPress[3] docker and making possible to mount my stuff inside wp-content. It works nicely! I can have different containers with different WordPress version to test my themes and plugins.

To work in this way, it’s necessary to have docker software and a container running mysql or mariadb.

Getting the images

The command to get docker images from docker hub is docker pull. Docker images lives on a registry, like hub.docker.com. But it’s possible to host own docker registry.

$ docker pull mariadb
$ docker pull montefuscolo/wordpress

Create and run a MariaDB container

The command to create and run containers from images is docker run. It’s necessary run a database like MariaDB before run WordPress. The MariaDB[4] instructions at docker hub has more detailed options for example below.

docker run --name mariadb \
           -e MYSQL_ROOT_PASSWORD=topsecretpassword \
           -p 3306:3306 \
           -i \
           mariadb
Parameter Meaning
--name mariadb will be the container name
-e MYSQL_ROOT_PASSWORD=topsecretpassword define variable that stores root password
-p 3306:3306 forward port 3306 on host to port 3306 on container
-i interactive, do not dettach from shell
mariadb the name of image pulled from docker hub

Create and run a WordPress container

The example below refers to project called Awesome Site. A wordpress container will be created and linked with mariadb running container.

$ docker run --name awesomesite \     
           --link mariadb:mysql \
           -e WORDPRESS_DB_USER=awesomeuser \
           -e WORDPRESS_DB_NAME=awesomedbname \
           -e WORDPRESS_DB_PASSWORD=awesomepassword \
           -e WORDPRESS_WP_DEBUG=true \
           -v $PWD/src/wp-content:/var/www/html/wp-content \
           -p 80:80 \
           -i \
           montefuscolo/wordpress
Parameter Meaning
--name awesomesite will be the container name
--link mariadb:mysql link containers, but wordpress will call mariadb of mysql
-e WORDPRESS_DB_USER=awesomeuser database user that will be inserted on wp-config.php
-e WORDPRESS_DB_NAME=awesomedbname database name that will be inserted on wp-config.php
-e WORDPRESS_DB_PASSWORD=awesomepassword database password that will be inserted on wp-config.php
-e WORDPRESS_WP_DEBUG=true turns WP_DEBUG=true and disables opcache
-v $PWD/src/wp-content:/var/www/html/wp-content will mount host folder over container folder
-p 80:80 forward port 80 on host to port 80 on container
-i interactive, do not dettach from shell
montefuscolo/wordpress the name of image pulled from docker hub

Now is possible to access a WordPress site at http://localhost on your web browser. You can also modify your content on you favorite editor and refresh browser to see modifications.

References

1 – https://www.docker.com/
2 – https://hub.docker.com/montefuscolo/wordpress/
3 – https://hub.docker.com/_/wordpress/
4 – https://hub.docker.com/_/mariadb/