Saturday, August 10, 2013

Google's two factor authenticator / Autenticação com 2 factores da Google

This article is written in Portuguese and English (original article here)
Este artigo está escrito em Inglês e Português (artigo original aqui)


English version:

Introduction

It should not be a surprise to anyone that I'm a big fan of PAM (plugable authentication modules). I've written several articles about it in this blog. This time I'll pick up a Google project and show you how to glue it together with Informix to achieve more security for your connections. And this happens thanks to PAM obviously.
Google authenticator is a project that implements one time password (OTP) generators for several mobile platforms. In practice you'll use your smartphone as a token generator. This avoids the need of a specific piece of hardware and has the advantages of being open source and making available a server side PAM library. Meaning you can integrate it with anything that supports PAM. This means your SSH daemon and naturally your favorite database server software.
Google authenticator can be seen and used as a component of a multi-factor authentication mechanism. This implies that a user must present (and I quote) "two or more of the three authentication factors: a knowledge factor ("something the user knows"), a possession factor ("something the user has"), and an inherence factor ("something the user is"). This article will show you how to configure Informix for two factor authentication. In our scenario we'll use a traditional password (something the user knows) and Google's authenticator as the second factor (something the user has). In the future, if the rumors about the introduction of biometric readers (like finger print readers) on mobile phones becomes a reality, it may be possible to extend this to three factors (something the user is).
It's possible that we'll see more services using this kind of technology. Just recently Twitter introduces two factor authentication by sending a request to their app when you try to login in their web site. The user will need to authorize that connection by using the twitter app on an authorized phone. Essentially this implements the same concept, but in an easier way.

The code

To start with this, we need to get the code for the PAM library. We can find it in Google's authenticator website. So I downloaded the package into a Linux VM, and uncompressed it:
[root@kimball google_authenticator]# pwd;ls -lia;tar -xjvf libpam-google-authenticator-1.0-source.tar.bz2
/usr/local/google_authenticator
total 44
1671484 drwxr-xr-x  2 root root  4096 Aug  6 11:43 .
1605654 drwxr-xr-x 14 root root  4096 Aug  6 09:46 ..
 327870 -rw-r--r--  1 root root 32708 Aug  6 11:40 libpam-google-authenticator-1.0-source.tar.bz2
libpam-google-authenticator-1.0/base32.c
libpam-google-authenticator-1.0/demo.c
libpam-google-authenticator-1.0/google-authenticator.c
libpam-google-authenticator-1.0/hmac.c
libpam-google-authenticator-1.0/pam_google_authenticator.c
libpam-google-authenticator-1.0/pam_google_authenticator_unittest.c
libpam-google-authenticator-1.0/sha1.c
libpam-google-authenticator-1.0/base32.h
libpam-google-authenticator-1.0/hmac.h
libpam-google-authenticator-1.0/sha1.h
libpam-google-authenticator-1.0/totp.html
libpam-google-authenticator-1.0/Makefile
libpam-google-authenticator-1.0/FILEFORMAT
libpam-google-authenticator-1.0/README
libpam-google-authenticator-1.0/utc-time/
libpam-google-authenticator-1.0/utc-time/app.yaml
libpam-google-authenticator-1.0/utc-time/utc-time.py

After this, following very simple instructions, I type "make install":

[root@kimball libpam-google-authenticator-1.0]# make install
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o google-authenticator.o google-authenticator.c
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o base32.o base32.c
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o hmac.o hmac.c
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o sha1.o sha1.c
gcc -g   -o google-authenticator google-authenticator.o base32.o hmac.o sha1.o  -ldl
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o pam_google_authenticator.o pam_google_authenticator.c
gcc -shared -g   -o pam_google_authenticator.so pam_google_authenticator.o base32.o hmac.o sha1.o -lpam
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o demo.o demo.c
gcc -DDEMO --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o pam_google_authenticator_demo.o pam_google_authenticator.c
gcc -g   -rdynamic -o demo demo.o pam_google_authenticator_demo.o base32.o hmac.o sha1.o  -ldl
gcc -DTESTING --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden        \
              -o pam_google_authenticator_testing.o pam_google_authenticator.c
gcc -shared -g   -o pam_google_authenticator_testing.so pam_google_authenticator_testing.o base32.o hmac.o sha1.o -lpam
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o pam_google_authenticator_unittest.o pam_google_authenticator_unittest.c
gcc -g   -rdynamic -o pam_google_authenticator_unittest pam_google_authenticator_unittest.o base32.o hmac.o sha1.o -lc  -ldl
cp pam_google_authenticator.so /lib64/security
cp google-authenticator /usr/local/bin
[root@kimball libpam-google-authenticator-1.0]#

and we can verify that the two components were installed correctly:

[root@kimball libpam-google-authenticator-1.0]# ls -lia /lib64/security/pam_google* /usr/local/bin/google*
1212428 -rwxr-xr-x 1 root root 116777 Aug  6 11:50 /lib64/security/pam_google_authenticator.so
1613197 -rwxr-xr-x 1 root root  55498 Aug  6 11:50 /usr/local/bin/google-authenticator
[root@kimball libpam-google-authenticator-1.0]#

Setup

We have the PAM library and the google-authenticator binary that we'll use to generate a secret key for our user. So this is the next step. I'll not do this with user "informix", because the engine will always ignore PAM for informix user when connecting locally. But before, let's check the other fundamental component of the solution: Your smartphone. Currently I use Android, but the app is available to iOS and Blackberry. You should know how to find and install the app. For Android devices it's available on GooglePlay store. The app for Android and iOS supports both reading a QR code or manual introduction of the secret key generated by the google-authenticator binary. For Blackbery, according to the Google Authenticator website, it only supports manual introduction.

Assuming the app is properly installed, we can proceed with the server side configuration. As mentioned above, the next step is to generate the secret key between the server account and the mobile app. For that we use the google-authenticator binary installed before. When we run it it outputs the information needed and also makes some questions. I will not dig into those as you can find out more in the documentation:

-bash-3.2$ google-authenticator

Do you want authentication tokens to be time-based (y/n) y
https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/fnunes@kimball%3Fsecret%3D6LWH24ZIUDY46HJU
Your new secret key is: 6LWH24ZIUDY46HJU
Your verification code is
649019
Your emergency scratch codes are:
  38268905
  24335468
  11497220
  81653596
  89796862

Do you want me to update your "/opt/fnunes/.google_authenticator" file (y/n) y

Do you want to disallow multiple uses of the same authentication
token? This restricts you to one login about every 30s, but it increases
your chances to notice or even prevent man-in-the-middle attacks (y/n) n

By default, tokens are good for 30 seconds and in order to compensate for
possible time-skew between the client and the server, we allow an extra
token before and after the current time. If you experience problems with poor
time synchronization, you can increase the window from its default
size of 1:30min to about 4min. Do you want to do so (y/n) n

If the computer that you are logging into isn't hardened against brute-force
login attempts, you can enable rate-limiting for the authentication module.
By default, this limits attackers to no more than 3 login attempts every 30s.
Do you want to enable rate-limiting (y/n) y
-bash-3.2$

It shows us a URL and the key. If we have a library installed (libqrencode) it will also show a QR code that we can use directly with the phone. If we open the URL in a browser it will show a QR code that we can use in the app to install the key. Otherwise we need to introduce the key manually. Either way it's pretty simple and in a minute you'll have a token (or one time password) generator on your mobile phone:

After this we need to go and configure our informix instance to take advantage of this. For that I'll create a new listener port using PAM. As you probably already know, this is done by altering the $INFORMIXSQLHOSTS file and add a line similar to this:

tpch_pam     onsoctcp             kimball.onlinedomus.net     1527   s=4,pam_serv=(ids_pam_service),pamauth=(challenge)


Field by field:
  • tpch_pam
    The INFORMIXSERVER name for this alias
  • onsoctcp
    The protocol to be used
  • kimball.onlinedomus.net
    The hostname
  • 1527
    The unique TCP port number for this listener
  • s=4,pam_serv=(ids_pam_service),pamauth=(challenge)
    options field. s=4 forces PAM usage. The PAM service name is "ids_pam_service" and the PAM mode will be "challenge" 
After the $INFORMIXSQLHOSTS we must make sure that the name "ids_pam_service" is configured in the PAM configuration. Being Linux, this means having a file called ids_pam_service in /etc/pam.d. The content of the file will be:

auth       required     pam_unix.so
auth       required     pam_google_authenticator.so
account    required     pam_unix.so

This will be used for our "basic by the book" configuration. Later I'll explain some issues with it, and will show you a better way to use it. As mentioned in other articles we just need "auth" and "account" configuration lines. And we're "stacking" modules (Google and pam_unix) so that we achieve the "two" factor authentication. First we'll test the unix password and then the Google's authenticator token or code (the one time password)

Next we need to make sure "tpch_pam" is configured in the $INFORMIXDIR/etc/$ONCONFIG file in the parameter DBSERVERALIAS and after that we can start the listener with:

onmode -P start tpch_pam

Testing

So, if we have everything setup, it should work. Let's test it with dbaccess:

tpch@kimball:fnunes-> id;hostname;echo $INFORMIXSERVER
uid=1002(fnunes) gid=1002(fnunes) groups=1002(fnunes)
kimball
tpch
tpch@kimball:fnunes-> dbaccess - -
> CONNECT TO 'sysmaster@tpch_pam' user 'fnunes';
   ENTER PASSWORD:
Password:
Verification code:

Connected.

> SELECT USER, * FROM syscfgtab WHERE cf_name LIKE 'DBSERVER%';



(expression)  fnunes
cf_id         2
cf_name       DBSERVERNAME
cf_flags      0
cf_original   tpch
cf_effective  tpch
cf_default    kimball

(expression)  fnunes
cf_id         77
cf_name       DBSERVERALIASES
cf_flags      0
cf_original   tpch_pam
cf_effective  tpch_pam
cf_default

2 row(s) retrieved.

> tpch@kimball:fnunes->

Firts impression is good. It works. But there's something weird. If you take a close look you'll see that once I do a CONNECT, dbaccess asks me for "ENTER PASSWORD:". Then it asks me for "Password:" and finally for "Verification code:". Three prompts... I hope you understand two of them but not the repeated request for password. Let me explain. By default, whenever we try to CONNECT dbaccess will ask for a password. That's the first prompt. Actually, with this configuration (challenge mode) we can enter whatever we want here... it will be ignored. Than we start the PAM stack layer. And the first module is pam_unix.so. Since we're not sending it any password (more about this later) it asks us for the password ("Password:" prompt). Here you have to write the system user password. After that we move to the second module, the Google authenticator module and it behaves the same way. Since it doesn't have a password it asks for one ("Verification code:" prompt). And here we need to take a look into our smartphone and copy the current generated token (or one time password). After that we're logged in. Both passwords were verified (one for each module in the stack).
This behavior justifies the "challenge" mode. The module sends a challenge back to the engine, and the engine send it back to the client (dbaccess in our case) and the client must have registered a callback function to handle the challenges and user responses. Basically what it does in dbaccess is to echo the challenge and read the response, and finally sending it back to the engine which sends it back to the module for verification.
Although it works, it's a bit ackward and needs a client side function to handle the challenges. This means we could possibly have to change the application. dbaccess already knows how to handle it, but other clients wouldn't know what to do.
Informix APIs have functions to register a callback function. But again, code changes are never welcome. So, let's see what we can do...

Improvement

As you probably know (I mentioned it in previous PAM articles), Informix pam can be configured in "password" or "challenge" mode. The documentation sometimes leads us to think that for this kind of usage we need to use challenge. But in fact it really depends on the modules you use and the options they provide. In our case I noticed that Google's authenticator module supports two interesting options:
  • try_first_pass
    makes it check the PAM framework for a previously supplied password
  • forward_pass
    makes it smart... if you provide a password composed by the concatenation of the system password and the verification token, it will try to split them, verify the code and send the rest through the PAM stack of modules
I also noticed pam_unix.so supports both try_first_pass and use_first_pass. So, what all this suggests is that assuming we have a system password like "mypasswd" and a verification code like "096712" we could use a composed password "mypasswd096712", give it to the dbaccess password request, and don't be bothered by each module prompts. let's change the configuration and test again. The file /etc/pam.d/ids_pam_service becomes this:

auth       required     pam_google_authenticator.so try_first_pass forward_pass
auth       required     pam_unix.so use_first_pass
account    required     pam_unix.so

And in INFORMIXSQLHOSTS we'll change "challenge" for "password":

tpch_pam     onsoctcp             kimball.onlinedomus.net     1527   s=4,pam_serv=(ids_pam_service),pamauth=(password)

And now let's restart the listener and repeat the test:

tpch@kimball:root-> onmode -P restart tpch_pam


tpch@kimball:fnunes-> dbaccess - -
> CONNECT TO 'sysmaster@tpch_pam' user 'fnunes';
   ENTER PASSWORD:

Connected.

> SELECT USER, * FROM syscfgtab WHERE cf_name LIKE 'DBSERVER%';

(expression)  fnunes
cf_id         2
cf_name       DBSERVERNAME
cf_flags      0
cf_original   tpch
cf_effective  tpch
cf_default    kimball

(expression)  fnunes
cf_id         77
cf_name       DBSERVERALIASES
cf_flags      0
cf_original   tpch_pam
cf_effective  tpch_pam
cf_default

2 row(s) retrieved.

> tpch@kimball:fnunes->

And voilá.... The first module now is Google authenticator. It gets the double password from the stack, extracts it's part (it knows it's the last 6 digits), verifies it, and sends the rest to the second module (pam_unix) that validates the password in the system

Now... we've seen that dbaccess does some magic... Because it knows we're dealing with a PAM port. So, to be absolutely sure this is transparent for the applications, let's try an external JDBC connection that has no knowledge that it's a PAM enabled port:

Success!
There not much more we can do. It's basic and very simple to setup. It shows Informix flexibility. And because it's PAM you can of course enrich it with additional modules if you like

Considerations

There are many things to note about this subject. First we could wonder about the usage cases for something like this. A few ideas come to mind:

  • added security for privilege users. You could assume that applications only connect trough  a safer network, using normal authentication, but DBSAs may need to connect from the "external" world and it requires added security
  • You can use it to construct a "double" password and have part of if available to the application code and let the user introduce the verification code. This would prevent a user to authenticate from outside the application (because the user would never know first password components, but on the other hand an application manager would not be able to impersonate the user even if he got to know the user password)
  • Other extended uses could be achieved by tweaking the module code.
It's important to keep in mind that as with any security related component you should consider carefully what you need and discuss the possibilities with security conscious people. I did a sort of brain storming with two ex-Informix DBAs that now work in the security team and some interesting points were raised. Among them, here are a few:

  1. This sort of token generator as opposed to specialized hardware like RSA tokens
    • Possible advantages of this method:
      • You probably notice you lost your smartphone faster than if you lost a specialized token
      • It's cheaper
      • If the device needs renewal, this method looks simpler (a user can do it once he gets the new phone)
      • You don't depend on any external supplier
    • Possible disadvantages of this method:
      • It seems easier to remotely "hack" a smartphone than to compromise the security of  a specialized hardware token
      • The application could provide some security measure to prevent unauthorized access to the generated codes to anybody who has physical access to the phone. Note this also happens on the hardware token, and with a phone you could always protect it with PIN or pattern code. This is not exactly a disadvantage comparing to hardware tokens, but could be something to improve
  2.  This would be hard to use for non-interactive processes. Unless, and I believe this could be a possibility, that we work the other way around... Meaning we have the code generator inside the applications servers, and that we setup a callback function to answer the module challenge. This would possibly avoid the usage of the application user from outside the application server environment
  3. A generic issue with any two factor authentication mechanism is that ideally the second factor should use a different communication channel from the first. That doesn't happen here, and this allows for man in the middle attacks
  4. Another point, which can be related to the one before, is the possibility to use, or not, the same token in a certain time interval (even a short one). The codes generated by Google authenticator are valid by default during 30 seconds (to allow some time for the user to introduce the code and also to compensate for small clock differences between client and server). The module allows that the code before and after the correct one to be used, so the time interval becomes 1m30s. But all this can be configured, and we even have the possibility of not allowing the same code to be used twice. This however will limit the ability of making more than one login each 30 seconds

Issues found

During the preparation and testing for this article I've faced two main issues:
  1. On first attempts I got the "Invalid verification code" error from the module. As the documentation says this is usually caused by clock synchronization issues between the server side and the mobile app side. After some checking I've found that I was mixing clock time with timezone offsets and it caused too much difference (the module allows configuration for some small difference)
  2. As usual with PAM and PAM modules the hardest part was debugging. Most modules tend to be very quiet about the errors. Not sure why, but on first attempts of concatenating user password with the verification code I was attempting the wrong order (code + password) instead of the proper order (password + code). I ended up looking into the code and changing it to be much more verbose.
Another possible issue is that at the moment, there is no App for Windows phones. And apparently they are grwoing in the market (according to recent studies, they shipped more units than Blackberry)

Acknowledgements

It's not unusual that I discuss some aspects while working on new articles. I recall having some help from several IBM colleagues, some of them already mentioned here once or twice. In this case I had the pleasure to discuss this subject with two ex-Informix DBAs, with whom I worked for several years on a customer team. Now they're working in the security team and the first time I heard about Google authenticator was during a chat with one of them. During the writing of this article and before it was published we interacted a couple of times. Several aspects of the post should be credited to them. So for that, and for the good time we spent working together (we still do occasionally because they have a long history in this customer and there are always subjects where we can work together) a big "Thank you!" to Daniel Valente and Rui Mourão.


Versão Portuguesa:

Introducão 

Não será uma surpresa para ninguém se disser que sou um grande fã do PAM (plugable authentication modules). Já escrevi vários artigos sobre o tema aqui no blog. Desta vez vou partir de um projeto da Google e mostrar como o usar em conjunto com o Informix para aumentar a segurança das conexões. E tudo graças ao PAM naturalmente.
O Google authenticator é um projeto que implementa um gerador de senhas descartáveis (one time passwords - OTP) para várias plataformas móveis. Na prática, transforma um smartphone num gerador se senhas (tokens). Isto evita a necessidade de uma peça física e tem as vantagens de ser open source e disponibilizar um módulo PAM sob a forma de uma biblioteca dinâmica. Isto permite que seja integrável com qualquer sistema que suporte PAM. Tanto pode ser um servidor de SSH como a sua base de dados favorita.
O Google authenticator pode ser visto como um componente de um mecanismo de autenticação multifatorial. Traduzindo, isto significa que o utilizador deve apresentar (e cito traduzindo) "dois ou mais dos três fatores de autenticação: um fator de conhecimento ("algo que o utilizador sabe"), um fator de posse ("algo que o utilizador tem") e um fator de inerência ("algo que o utilizador é"). Este artigo mostrará como configurar o Informix para uma autenticação de dois fatores. No nosso cenário usaremos uma senha tradicional (algo que o utilizador sabe) e o Google authenticator como segundo fator (algo que o utilizador tem). No futuro, se os rumores sobre a introdução de leitores de dados biométricos (impressão digital por exemplo) em dispositivos móveis se tornar uma realidade, poderá ser possível estender para três fatores (algo que o utilizador é).
É possível que vejamos mais serviços a usar este tipo de tecnologia. Ainda recentemente o Twitter introduziu a autenticação de dois fatores, usando o envio de um pedido à sua App quando tenta fazer login no seu site. O utilizador tem de autorizar a ligação usando a aplicação do Twitter num telefone autorizado. Na essência é o mesmo conceito, mas talvez de uma forma ainda mais fácil para o utilizador.

O código

Para começar, necessitamos da biblioteca PAM. Podemos encontrá-la no website do Google authenticator. Fiz a transferência do pacote e coloquei-o numa máquina virtual Linux, tendo de seguida descomprimido:
[root@kimball google_authenticator]# pwd;ls -lia;tar -xjvf libpam-google-authenticator-1.0-source.tar.bz2
/usr/local/google_authenticator
total 44
1671484 drwxr-xr-x  2 root root  4096 Aug  6 11:43 .
1605654 drwxr-xr-x 14 root root  4096 Aug  6 09:46 ..
 327870 -rw-r--r--  1 root root 32708 Aug  6 11:40 libpam-google-authenticator-1.0-source.tar.bz2
libpam-google-authenticator-1.0/base32.c
libpam-google-authenticator-1.0/demo.c
libpam-google-authenticator-1.0/google-authenticator.c
libpam-google-authenticator-1.0/hmac.c
libpam-google-authenticator-1.0/pam_google_authenticator.c
libpam-google-authenticator-1.0/pam_google_authenticator_unittest.c
libpam-google-authenticator-1.0/sha1.c
libpam-google-authenticator-1.0/base32.h
libpam-google-authenticator-1.0/hmac.h
libpam-google-authenticator-1.0/sha1.h
libpam-google-authenticator-1.0/totp.html
libpam-google-authenticator-1.0/Makefile
libpam-google-authenticator-1.0/FILEFORMAT
libpam-google-authenticator-1.0/README
libpam-google-authenticator-1.0/utc-time/
libpam-google-authenticator-1.0/utc-time/app.yaml
libpam-google-authenticator-1.0/utc-time/utc-time.py

Depois disso, seguindo instruções muito simples basta fazer "make install":

[root@kimball libpam-google-authenticator-1.0]# make install
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o google-authenticator.o google-authenticator.c
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o base32.o base32.c
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o hmac.o hmac.c
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o sha1.o sha1.c
gcc -g   -o google-authenticator google-authenticator.o base32.o hmac.o sha1.o  -ldl
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o pam_google_authenticator.o pam_google_authenticator.c
gcc -shared -g   -o pam_google_authenticator.so pam_google_authenticator.o base32.o hmac.o sha1.o -lpam
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o demo.o demo.c
gcc -DDEMO --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o pam_google_authenticator_demo.o pam_google_authenticator.c
gcc -g   -rdynamic -o demo demo.o pam_google_authenticator_demo.o base32.o hmac.o sha1.o  -ldl
gcc -DTESTING --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden        \
              -o pam_google_authenticator_testing.o pam_google_authenticator.c
gcc -shared -g   -o pam_google_authenticator_testing.so pam_google_authenticator_testing.o base32.o hmac.o sha1.o -lpam
gcc --std=gnu99 -Wall -O2 -g -fPIC -c  -fvisibility=hidden  -o pam_google_authenticator_unittest.o pam_google_authenticator_unittest.c
gcc -g   -rdynamic -o pam_google_authenticator_unittest pam_google_authenticator_unittest.o base32.o hmac.o sha1.o -lc  -ldl
cp pam_google_authenticator.so /lib64/security
cp google-authenticator /usr/local/bin
[root@kimball libpam-google-authenticator-1.0]#

e podemos verificar que os dois componentes foram instalados corretamente:

[root@kimball libpam-google-authenticator-1.0]# ls -lia /lib64/security/pam_google* /usr/local/bin/google*
1212428 -rwxr-xr-x 1 root root 116777 Aug  6 11:50 /lib64/security/pam_google_authenticator.so
1613197 -rwxr-xr-x 1 root root  55498 Aug  6 11:50 /usr/local/bin/google-authenticator
[root@kimball libpam-google-authenticator-1.0]#

Configuração

Temos a biblioteca PAM e o binário google-authenticator que iremos usar para gerar uma chave secreta para cada utilizador. Portanto este será o próximo passo. Não irei usar o utilizador "informix" porque o motor ignora sempre o PAM para conexões locais desse utilizador. Mas antes vamos apenas verificar o outro componente fundamental da solução: O seu smartphone. Atualmente utilizo Android, mas a aplicação está também disponível para iOS e Blackberry. Deverá saber como encontrar e instalar a App. Para dispositivos Android está disponível na loja Google Play. A App para Android e iOS suporta a introdução manual da chave secreta bem como a leitura de um código QR. Para Blackberry, segundo o website apenas suporta a introdução manual.
Assumindo que a App está devidamente instalada, podemos prosseguir com a configuração do lado do servidor. Como mencionado anteriormente o próximo passo é a geração da chave secreta que será partilhada entre o servidor e a aplicação móvel. Para isso usamos o binário google-authenticator instalado acima. Quando o executamos mostra a informação necessária e coloca também algumas questões. Não me vou debruçar sobre as mesmas, pois a documentação tem explicações completas:
-bash-3.2$ google-authenticator

Do you want authentication tokens to be time-based (y/n) y
https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/fnunes@kimball%3Fsecret%3D6LWH24ZIUDY46HJU
Your new secret key is: 6LWH24ZIUDY46HJU
Your verification code is 649019

Your emergency scratch codes are:
  38268905
  24335468
  11497220
  81653596
  89796862

Do you want me to update your "/home/fnunes/.google_authenticator" file (y/n) y

Do you want to disallow multiple uses of the same authentication
token? This restricts you to one login about every 30s, but it increases
your chances to notice or even prevent man-in-the-middle attacks (y/n) n

By default, tokens are good for 30 seconds and in order to compensate for
possible time-skew between the client and the server, we allow an extra
token before and after the current time. If you experience problems with poor
time synchronization, you can increase the window from its default
size of 1:30min to about 4min. Do you want to do so (y/n) n

If the computer that you are logging into isn't hardened against brute-force
login attempts, you can enable rate-limiting for the authentication module.
By default, this limits attackers to no more than 3 login attempts every 30s.
Do you want to enable rate-limiting (y/n) y
-bash-3.2$

Como vemos mostra tanto um URL como a chave. Se tivermos uma biblioteca instalada (libqrencode) irá mostrar logo um código QR que podemos usar directamente com o telefone.
Se abrirmos o URL num browser a página obtida mostrará o mesmo código QR, que podemos usar na aplicação móvel para instalar a chave secreta. Alternativamente podemos introduzi-la manualmente. Qualquer das formas é bastante fácil e num instante terá um gerador de senhas descartáveis no seu telefone móvel:

Depois disto temos de configurar a nossa instância Informix para tirar proveito do mecanismo. Para tal irei criar um listener que use PAM. Isto é feito adicionando uma linha semelhante a esta ao $INFORMIXSQLHOSTS:

tpch_pam     onsoctcp             kimball.onlinedomus.net     1527   s=4,pam_serv=(ids_pam_service),pamauth=(challenge)

Campo por campo:
  • tpch_pam
    O INFORMIXSERVER para este alias
  • onsoctcp
    O protocolo de comunicação a usar
  • kimball.onlinedomus.net
    O nome da máquina
  • 1527
    O porto TCP único que será usado por este listener
  • s=4,pam_serv=(ids_pam_service),pamauth=(challenge)
    Campos de opções: s=4 indica a utilização de PAM. O nome do serviço PAM será "ids_pam_service" e o modo de funcionamento PAM será "challenge" 
Depois de alterado o $INFORMIXSQLHOSTS  temos de configurar um serviço PAM na máquina com o nome "ids_pam_service". Sendo Linux isto é feito criando um ficheiro com o mesmo nome em /etc/pam.d/ cujo conteúdo será:

auth       required     pam_unix.so
auth       required     pam_google_authenticator.so
account    required     pam_unix.so

Isto será usado para a nossa configuração "básica seguindo as regras". Mais tarde vou explicar alguns problemas deste método e mostrarei uma melhor forma de configurar. Como já mencionei noutros artigos apenas precisamos das linhas de configuração para "auth" e "account". Estamos a "empilhar módulos" (Google e pam_unix) para que tenhamos os "dois" fatores de autenticação. Primeiro iremos testar a senha Unix e depois o código de verificação Google (a senha descartável)

De seguida temos de garantir que "tpch_pam" está configurado no ficheiro $INFORMIXDIR/etc/$ONCONFIG no parâmetro DBSERVERALIAS e após isso podemos iniciar o listener com:

onmode -P start tpch_pam

Testes

Portanto, se tudo estiver bem configurado deverá funcionar. Vamos testar com o dbaccess:

tpch@kimball:fnunes-> id;hostname;echo $INFORMIXSERVER
uid=1002(fnunes) gid=1002(fnunes) groups=1002(fnunes)
kimball
tpch
tpch@kimball:fnunes-> dbaccess - -
> CONNECT TO 'sysmaster@tpch_pam' user 'fnunes';
   ENTER PASSWORD:
Password:
Verification code:

Connected.

> SELECT USER, * FROM syscfgtab WHERE cf_name LIKE 'DBSERVER%';



(expression)  fnunes
cf_id         2
cf_name       DBSERVERNAME
cf_flags      0
cf_original   tpch
cf_effective  tpch
cf_default    kimball

(expression)  fnunes
cf_id         77
cf_name       DBSERVERALIASES
cf_flags      0
cf_original   tpch_pam
cf_effective  tpch_pam
cf_default

2 row(s) retrieved.

> tpch@kimball:fnunes->

A primeira impressão é boa. Funciona. Mas há algo estranho. Se olharmos com cuidado vemos que assim que fazemos o CONNECT, o dbaccess pede-me para "ENTER PASSWORD:". Depois pede "Password:" e finalmente pede "Verification code:". Três pedidos... Espero que dois deles sejam óbvios, mas não o pedido de password repetido. Deixe-me explicar. Por omissão, sempre que tentamos um CONNECT o dbaccess pede uma senha. Esse é o primeiro pedido. Na verdade, com esta configuração (challenge mode) podemos introduzir o que quisermos... será ignorado. Depois entramos na pilha de módulos PAM. E o primeiro módulo que temos configurado é o pam_unix.so. Como não lhe estamos a passar nenhuma senha "internamente" (mais sobre isto adiante), ele pede-nos uma senha (pedido "Password:"). Aqui temos de inserir a senha de sistema do utilizador. Depois passamos para o segundo módulo, o do Google authenticator, que se comporta da mesma forma. Como não lhe é disponibilizada uma senha pede-a (pedido "Verification code:"). Nesta fase temos de olhar para o smartphone e transcrever o número que foi gerado para esta conta (a nossa senha descartável). Após isto, completa-se o login. Ambas as senhas foram validadas (uma por cada módulo).
Este comportamento justifica o challenge mode. O(s) módulo envia um desafio ao motor que o encaminha para o cliente (no nosso caso o dbaccess) e o cliente tem de ter uma função para lidar com estes desafios e as respostas do utilizador. Essa função designa-se por função de callback. No caso do dbaccess a função genérica apenas recebe o desafio, imprime-o, lê a resposta do utilizador e envia-a para o motor que depois responde ao módulo para verificação.
Embora isto funcione, é um pouco estranho e necessita que o cliente tenha uma função que lide com os desafios. Isto quer dizer que poderá ser necessário alterar a aplicação. O dbaccess já sabe o que fazer, mas outros clientes não saberiam.
As APIs do Informix têm funções para registar a função de callback. Mas de qualquer forma, alterações nunca são bem vindas... Vejamos o que podemos fazer.

Melhorias

Como já saberá (já o mencionei noutros artigos sobre PAM), o Informix pode ser configurado em modo "password" ou "challenge". A documentação por vezes leva-nos a pensar que para este tipo de implementação temos de usar challenge. Mas na realidade isto depende dos módulos que vamos usar e das opções que disponibilizam. No nosso caso verifiquei que o Google authenticator suporta duas opções interessantes:
  • try_first_pass
    obriga-o a verificar na infra-estrutura PAM se foi passada anteriormente uma senha
  • forward_pass
    torna-o inteligente... Se fornecermos uma senha que seja a concatenação da senha de sistema com o código de verificação, o módulo vai separá-los, verifica a parte dele, e passa o resto para os módulos seguintes
Notei igualmente que o módulo pam_unix.so também suporta as opções try_first_pass e use_first_pass. Portanto o que tudo isto sugere é que se tivermos uma senha de sistema "MinhaPass" e um código como "096712" poderia-mos usar uma senha composta "MinhaPass096712", dá-la como senha ao dbaccess e não nos preocupamos mais com os pedidos (desafios) de cada módulo. Vamos mudar a configuração e testar novamente. O ficheiro /etc/pam.d/ids_pam_service passa a ter:

auth       required     pam_google_authenticator.so try_first_pass forward_pass
auth       required     pam_unix.so use_first_pass
account    required     pam_unix.so

E no ficheiuro $INFORMIXSQLHOSTS mudamos "challenge" para "password":

tpch_pam     onsoctcp             kimball.onlinedomus.net     1527   s=4,pam_serv=(ids_pam_service),pamauth=(password)

E vamos re-iniciar o listener e testar:

tpch@kimball:root-> onmode -P restart tpch_pam


tpch@kimball:fnunes-> dbaccess - -
> CONNECT TO 'sysmaster@tpch_pam' user 'fnunes';
   ENTER PASSWORD:

Connected.

> SELECT USER, * FROM syscfgtab WHERE cf_name LIKE 'DBSERVER%';

(expression)  fnunes
cf_id         2
cf_name       DBSERVERNAME
cf_flags      0
cf_original   tpch
cf_effective  tpch
cf_default    kimball

(expression)  fnunes
cf_id         77
cf_name       DBSERVERALIASES
cf_flags      0
cf_original   tpch_pam
cf_effective  tpch_pam
cf_default

2 row(s) retrieved.

> tpch@kimball:fnunes->

E voilá....  O primeiro módulo agora passou a ser o Google authenticator. Recebe a senha "dupla" internamente pelo PAM (inicializado pelo motor), extrai a parte que lhe diz respeito (últimos seis caracteres), verifica esse código como senha descartável, e envia o resto para o segundo módulo (pam_unix) que valida a senha no sistema.
Entretanto... nós vimos que o dbaccess faz alguma mágica... Porque sabe que está a lidar com um porto PAM. Para ficarmos absolutamente seguros que este método é transparente para a aplicação, vamos tentar uma ligação externa com JDBC, que não terá qualquer noção que está a falar com um porto PAM:

Sucesso!
E não há muito mais a fazer. É básico e muito simples de configurar. Mostra a flexibilidade do Informix. E como usa PAM, podemos enriquecer a solução adicionando outros módulos se desejarmos

Considerações

Existem muitos aspetos a considerar neste assunto. Primeiro podemos imaginar os cenários de utilização para isto. Vêm-me algumas ideias à cabeça:
  • Segurança adicional para utilizadores priveligiados. Podemos assumir que as aplicações se conectam apenas por uma rede mais segura, utilizando a autenticação normal, mas os DBSAs podem ter de se ligar através de uma interface mais exposta que por isso justifique segurança adicional
  • Pode usar isto para trabalhar com uma senha "dupla" em que parte esteja disponível internamente na aplicação e os utilizadores apenas fornecem o código de verificação. Isto impediria os utilizadores de se autenticarem fora da aplicação (porque o utilizador não teria conhecimento sobre o outro componente da senha, ao mesmo tempo que o gestor aplicacional não poderia assumir a identidade do utilizador pois não teria o código de verificação)
  • Podem criar-se outros cenários bem mais complexos se estivermos dispostos a mexer no código do módulo
É importante ter sempre presente que como em qualquer assunto relacionado com segurança, temos de avaliar cuidadosamente as necessidades e discutir as possibilidades com pessoas que entendam bem os mecanismos de segurança. Tive uma espécie de brain storming com dois ex-DBAs Informix que agora trabalham em segurança e alguns pontos interessantes foram levantados. Entre eles aqui ficam alguns:
  1. Este tipo de gerador de senhas descartáveis em oposição aos dispositivos especializados como os tokens RSA
    • Possíveis vantagens deste método:
      • Provavelmente mais rapidamente se apercebe da perda de um telemóvel que de um equipamento especializado
      • É mais barato
      • Se o dispositivo necessitar de substituição, este método parece mais simples (o próprio utilizador pode fazê-lo após ter um novo telefone na sua posse)
      • Não existe dependência de um fornecedor externo
    • Possíveis desvantagens deste método:
      • Parece mais fácil atacar remotamente um smartphone que comprometer a segurança de um aparelho específico
      • A própria App poderia disponibilizar algum mecanismo para dificultar o acesso não autorizado aos códigos por parte de quem tenha acesso físico ao aparelho. Note-se que isto também é um problema para os dispositivos específicos, e num telefone este pode ser protegido por um PIN ou pela introdução de um padrão de autenticação. Não será necessariamente uma desvantagem deste método, mas algo que poderia ser melhorado
      • Um telefone está mais sujeito a problemas que afetem o funcionamento do gerador que um dispositivo específico
  2. Seria difícil utilizar este método em processos não interativos. A menos, e acredito que isto fosse possível, que se trabalhe ao contrário... Ou seja, que tenhamos o gerador de códigos dentro dos servidores aplicacionais, e que se configure uma função de callback que responda ao desafio do módulo. Isto poderia evitar a utilização do utilizador aplicacional fora do ambiente dos servidores aplicacionais
  3. Um problema genérico da autenticaçao de dois fatores é que idealmente o segundo fator deverá utilizar um canal diferente do primeiro. Tal não acontece aqui e isso permite que um ataque do tipo man in the middle possa ser feito.
  4. Outra questão que pode estar relacionada com a de cima é a possibilidade ou não de reutilizar o mesmo código (senha descartável) num determinado período de tempo ainda que curto. As senhas geradas pelo Google authenticator têm por omissão uma duração de 30 segundos (para permitir que o utilizador tenha tempo de a introduzir e também para compensar algum desfazamento de relógios). O módulo permite ainda usar o código imediatemente antes e depois, passando o intervalo para 1m30. Mas tudo isto pode ser controlado, e inclusivamente podemos limitar cada código a apenas uma utilização. Isto no entanto limitará o utilizador a não fazer mais que um login em cada 30 segundos

Problemas encontrados

Durante a preparação e testes destes artigo encontrei dois problemas principais:
  1. Nas primeiras tentativas obtive o erro "Invalid verification code" gerado pelo módulo. Como é referido na documentação, isto é habitualmente causado por problemas de sincronização de relógios entre o servidor e o lado da aplicação móvel. Após algumas verificações percebi que estava a fazer confusão entre a hora do relógio e a timezone. Isto causava uma diferença de relógios demasiado grande (o módulo permite alguma configuração para pequenas diferenças)
  2. Como é habitual com PAM e os seus módulos, a parte mais difícil é perceber os erros. A maioria dos módulos tendem a ser bastante "secretos" em relação às causas dos erros. Por exemplo, e não sei bem porquê, nas primeiras tentativas de concatenar as duas senhas, assumi que a ordem era a contrária (código + senha de sistema) em vez da correta (senha de sistema + código). Naturalmente não funcionava mas foi difícil perceber porquê. Acabei por alterar o código do módulo para introduzir mensagens que permitissem o debug
Outro possível problema é que actualmente não há versão da App para telefones Windows que aparentemente estão em crescimento no mercado (segundo números recentes venderam-se recentemente mais telefones Windows que Blackberry)

Agradecimentos

Não é inédito que eu discuta alguns aspetos enquanto escrevo estes artigos. Lembro-me de já ter tido ajuda de vários colegas da IBM, alguns já aqui mencionados algumas vezes. Neste caso tive o prazer de discutir este assunto com dois ex-DBAs Informix, com quem já trabalhei durante vários anos numa equipa de um cliente. Agora ambos trabalham na equipa de segurança e a primeira vez que o tema do Google authenticator me chamou a atenção foi durante uma conversa com um deles. Enquanto escrevia este artigo interagi com eles várias vezes. Vários aspetos deste artigo devem-se a essas conversas. Portanto, por isso e pelos bons tempos em que trabalhámos juntos (ocasionalmente ainda o fazemos, pois ambos têm uma longa história nesse cliente e é frequente os assuntos cruzarem-se), um grande "Obrigado!" ao Daniel Valente e Rui Mourão

Monday, August 05, 2013

Strange bufwaits / bufwaits estranhos

This article is written in English and Portuguese (original article is here)
Este artigo está escrito em Inglês e Português (artigo original aqui)


English version:

Another recent customer situation triggered an investigation that may be helpful to others. We had a system with two instances, but only one was working. Database configuration was almost equal to other system, but the query performance was horrible in comparison. Sessions status had lots of "IO Wait", but also lots of "yield bufwait". All the monitoring with OS tools showed that we were facing severe IO performance issues (disks showed up at 100% utilization and data throughput was much lower than on similar systems).

So, the DBA team interacted with the system administration team and they identified several hardware and configuration issues that caused severe IO bottleneck. But even considering that those issues were being addressed by the system admin team, the DBA team was still concerned with the fact that we were seeing bufwaits. Accordingly to the manual, bufwaits means our session was trying to access a buffer which was locked by other session. But we could get those bufwaits even when only one query was running. So it was a bit strange.
As usual, if something doesn't look obvious we have to follow the details. First step was to run "onstat -b" during the query execution and try to find who is the owner of the buffer that we want to access. The output was similar to this:

IBM Informix Dynamic Server Version 11.50.UC9W2X1 -- On-Line -- Up 00:04:28 -- 111240 Kbytes

address  userthread flgs pagenum          memaddr  nslots pgflgs xflgs owner    waitlist

4421ec80 0          84   3:230791         4440e000 87     801    80    ffffffff 44d0bc90

The pagenum was constantly changing, but the owner was always "fffffff". Needless to say we don't have any thread with a rstcb address equal to "ffffffff". So we were stuck. But a bit of internal IBM investigation thought me that this means we're waiting for an IO (AIO or KAIO) thread. This didn't answer our question: If we're waiting on IO why doesn't it always show "IO Wait" but instead shows "yield bufwait"?

Well it appears the answer is pretty simple and it's related to the read ahead functionality. After some trial, I noticed that if I reduce the RA_PAGES and RA_THRESHOLD parameters, the thread will show much more "IO Wait" than "yield bufwait". If I increase it I get the opposite. Apparently the engine considers read ahead for the query. And because the disk access is slow, some buffers are consumed by the sqlexec before the IO request completes. So when the sqlexec thread identifies the buffer slot that it needs, it's already in the process of being read from the disk.
Later one of the DBAs used this to make some benchmarking about the best RA_PAGES and RA_THRESHOLD values to use. It was around 128 and 100 respectively. But beginning with 11.70 you should be better using AUTOREADAHED functionality instead.


Versão Portuguesa:
Uma situação recente num cliente despoletou uma investigação que se pode revelar interessante para outros. Tínhamos um sistema com duas instâncias mas apenas uma a correr. As configurações eram quase iguais a outro sistema, mas a velocidade de execução de uma query era péssima em comparação com o outro sistema. O estado das sessões mostrava constantemente "IO Wait", mas também muitos "yield bufwait". Toda a monitorização usando as ferramentas de sistema operativo indicavam que estávamos a ter problemas sérios de I/O (discos com 100% de utilização e métricas de transferência muito abaixo de sistemas semelhantes)

No seguimento, o DBA interagiu com a equipa de gestão de sistemas e foram identificados alguns problemas de hardware e configurações que causavam o problema de I/O. Mas mesmo considerando que esses temas iam ser resolvidos pela equipa de sistemas, a equipa de DBAs ainda estava preocupada com o facto de aparecerem bufwaits. Segundo o manual, bufwaits implica que a nossa sessão está a tentar aceder a um buffer que já está bloqueado por outra sessão. Mas os nossos bufwaits apareciam mesmo quando apenas esta query estava a correr no motor. Por isso parecia um pouco estranho.
Como é hábito, quando algo não parece bem, temos de verificar os detalhes. O primeiro passo foi executar um "onstat -b" durante a execução da query, e tentar quem é o dono dos buffers a que estamos a tentar aceder. O resultado era semelhante a isto:

IBM Informix Dynamic Server Version 11.50.UC9W2X1 -- On-Line -- Up 00:04:28 -- 111240 Kbytes

address  userthread flgs pagenum          memaddr  nslots pgflgs xflgs owner    waitlist

4421ec80 0          84   3:230791         4440e000 87     801    80    ffffffff 44d0bc90

O número da página estava naturalmente sempre a mudar, mas o owner era sempre "fffffff". Escusado será dizer que não temos nenhuma thread cujo rstcb seja ifual a "ffffffff". Por isso esávamos bloqueados. Um pouco mais de investigação na IBM disse-nos que o significado disto é que estávamos à espera de uma thread de I/O (AIO ou KAIO). Isto não respondia à nossa questão: "Se estamos à espera de I/O porque é que não mostra IO Wait em vez de "yield bufwait"?

Bom, aparentemente a resposta é bastante simples e está relacionada com a funcionalidade de read ahead. Após algumas tentativas verifiquei que se reduzir os parâmetros RA_PAGES e RA_THRESHOLD, a thread irá mostrar muito mais IO Wait que yield bufwait. Se os aumentar tenho o contrário. O motor estava a considerar que a query beneficiaria de read ahead. E porque o sistema de discos estava muito lento, alguns buffers eram consumidos pela thread sqlexec (que executa o SQL) antes que os pedidos de I/O se concluíssem. Assim, quando a thread sqlexec identifica o próximo buffer a usar, já existe um pedido de leitura para ele.
Mais tarde um dos DBAs utilizou esta situação para efetuar benchmark sobre os parâmetros RA_PAGES e RA_THRESHOLD. Os melhores valores seriam por volta dos 128 e 100 respetivamente. Mas a partir da 11.70 não deve ter de se preocupar, devendo deixar isto a cargo da funcionalidade de AUTOREADAHED.

Friday, August 02, 2013

SQLHOSTS options / Opções do SQLHOSTS

This article is written in English and Portuguese (full post here!)
Este artigo está escrito em Inglês e Português (artigo completo aqui!)

English version:
Recently I've been asked to setup some connection redirection for Informix clusters. In most situations we'll want to use the connection manager, but in this post I'm not going that deep. I'll just review something that is also related to the use of connection manager, but that has a lot more uses and that I've found many customers are not aware. I'm referring to the INFORMIXSQLHOSTS file options field. You can find the full documentation here in the InfoCenter. I'll just make some remarks about several options that can be extremely useful, and many people never thought about using.

  • r option (0|1)
    Only used on the client side. If it's set to 1, the client will look for .netrc file in the user's home directory that allows you to specify a user and password to connect to the database server
  • s option (0,1,2,3|4|6)
    Controls server side security settings. Check the documentation for full details, but:
    • 0 disables checking /etc/hosts.equiv and ~user/.rhosts (or their equivalents defined in REMOTE_SERVER_CFG and REMOTE_USER_CFG parameters)
    • 1 activates the central trust file (/etc/hosts.equiv or it's replacement REMOTE_SERVER_CFG) and deactivates the individual user file (~user/.rhosts or it's replacement defined in REMOTE_USER_CFG parameter)
    • 2 opposite of 1
    • 3 activates both files
    • 4 for PAM enabled ports
    • 6 restricts the port for replication connections only (HDR, RSS and ER)
  • k option (0|1)
    Activates (1) the TCP functionality of keep alive. Note that the keep alive interval, timeout and retries must be setup at the OS level. This can be very important when there are firewalls between the clients and the servers. In some cases, if the functionality is not activated (which can be controlled at the OS level), the firewalls may break the connection and an idle client will receive an error when it attempts to send a query after a long period of inactivity
As you can see on the documentation there are many more. But for the purpose of this post, I'm particularly interested in the group related options. The concept of groups was created for Enterprise Replication many years ago but we can use them for other purposes. Let's first see how to create a group:

my_group group - - i=100,c=0
my_server_1 onsoctcp my_hostname_1 1526 k=1,g=my_group
my_server_2 onsoctcp my_hostname_2 1526 k=1,g=my_group

So, here I have a group called "my_group", defined because I've use "group" in the protocol field. I don't use the hostname and service name or port (so I replace them with a "-") and I use two options:
  • i=100
    This is just a group id.We can use any unique number for each group
  • c=0
    This controls the way the clients trying to access INFORMIXSERVER=my_group handle the several servers in the group. This option can be setup to "0" or "1":
    • 0 means that it will always try to connect to the first server, and if it's not available it will try the second one and so on
    • 1 means it will randomly choose one of the servers that belong to the group and start trying from there until it finds one server available
Now, how is this useful? I'll give you two usage scenarios. First one is a cluster with primary server, an HDR server and an RSS server. Both secondary nodes are in read-only mode, we don't plan to change the roles between the servers, and naturally the chances that the HDR is more up to date than the RSS is higher. In this case we may want to redirect read-only clients preferably to the HDR instance, but in case of failure or planned maintenance we want the clients to be shifted to the RSS server. The following INFORMIXSQLHOSTS will do the trick:

my_read_only_group group - - i=100,c=0
my_hdr_instance onsoctcp my_hdr_host 1526 k=1,g=my_read_only_group
my_rss_instance onsoctcp my_rss_host 1526 k=1,g=my_read_only_group

And then just use INFORMIXSERVER=my_read_only_group. Clients will attempt to connect to my_hdr_instance first, but it that fails they'll retry the connection against my_rss_instance.
And there you have it... The most simple client redirection you can find. This has advantages and disadvantages:
  •  You don't need another piece of software (connection manager). It's terribly simple to setup
  • It works for clients with very old versions that can't handle connection managers in REDIRECT mode (maybe in a future post about connection manager I'll explain this)
  • It will not work if you change your instances roles (like primary to secondary and vice-versa)
  • Although you could set "c=1" to randomly distribute the connections between the two servers, it would not allow more sophisticated redirection rules allowed by the connection manager
Let's now see the second scenario. For improved flexibility you want to implement connection manager. But you know that once you do it, it will be a single point of failure. If it's down, you won't be able to connect to your servers, even if there is nothing wrong with them. Naturally the solution would be to setup two connection managers on different machines. But then how would you configure your clients? Well, very similar to the example above. But this time, we would have two listeners (one in each machine where the connection manager is running) and we'd want the clients to see and try to connect through both of them. So, let's assume we create the cm_1 and cm_2 services on two different hosts using the connection manager. In each of them cm_* will be an SLA that will redirect for the secondary servers. So, our client side INFORMIXSQLHOSTS would be:

my_read_only_group - - i=100,c=1
cm_1 onsoctcp cm1_hostname 1526 g=my_read_only_group
cm_2 onsoctcp cm2_hostname 1526 g=my_read_only_group

Our clients would be setup with INFORMIXSERVER=my_read_only_group and they would try each connection manager randomly.  After reaching a connection manager service, they would be redirected to the appropriate secondary server. The configuration on the CM will not be covered in this post.

There is only one more piece of information that may be important. By default, Informix clients wait very long (60 seconds) for a connection attempt. When you're dealing with connection redirection you probably don't want to waste so much time.... Let's face it, if a server does not reply within 10s (I'm accommodating DNS problems and so on) it will never answer. We can establish two environment variables to configure this:
  • INFORMIXCONTIME
    The timeout the client will wait for a connection establishment (in seconds)
  • INFORMIXCONRETRY
    The number of attempts the client will do within the INFORMIXCONTIME period

Versão Portuguesa:
Foi-me pedido recentemente que configurasse um redirecionamento de ligações para um cluster Informix. Na maioria das situações queremos usar o connection manager, mas neste artigo não irei tão longe. Vou apenas rever algo que embora também esteja relacionado com o uso do connection manager, tem muitos mais casos de utilização, e apercebi-me que muitos clientes não têm conhecimento disto. Estou a referir-me ao campo de opções do ficheiro $INFORMIXSQLHOSTS. Pode encontrar a documentação completa sobre isto aqui no InfoCenter. Vou apenas referir algumas notas sobre algumas das opções que verifico serem desconhecidas de muitos clientes.
  • opção "r" (0|1)
    É apenas usada do lado do cliente. Se estiver com o valor 1, o cliente irá procurar num ficheiro chamado .netrc, na $HOME do utilizador, os dados de ligação, especificamente o utilizador e password
  • opção "s" (0,1,2,3|4|6)
    Controla as configurações de segurança do lado do servidor. Consulte a documentação para mais informação, mas:
    • 0 desativa a verificação dos ficheiros /etc/hosts.equiv e ~user/.rhosts (ou os seus equivalentes definidos nos parâmetros REMOTE_SERVER_CFG e REMOTE_USER_CFG)
    • 1 ativa a validação do ficheiro central de trusts (/etc/hosts.equiv ou o seu substituto indicado em REMOTE_SERVER_CFG) e desativa o ficheiro individual (~user/.rhosts e o seu substituto definido em REMOTE_USER_CFG)
    • 2 é o oposto de 1
    • 3 ativa ambos os ficheiros
    • 4 para portos configurados com PAM
    • 6 restringe o porto a conexões relacionadas com replicação (HDR, RSS e ER)
  • opção "k" (0|1)
    Ativa (1) a funcionalidade de TCP keep alive. Note que o intervalo de timeout e tentativas têm de ser configurados a nível de sistema operativo. Esta opção apenas força uma flag na gestão de sockets, sendo o resto feito ao nível do SO. Isto pode ser muito importante quando existem firewalls entre os clientes e servidores. Em alguns casos, se a funcionalidade não estiver ativa, os firewalls podem quebrar as ligações após um período de inatividade. Nestes casos os clientes podem receber um erro ao tentarem utilizar uma conexão que já não existe.
Como pode verificar na documentação existem muito mais opções. Mas para o objetivo deste artigo estou particularmente interessado nas opções de grupo. O conceito de grupos foi criado para a Enterprise Replication há muitos anos atrás, mas podemos usá-los para outros fins. Vejamos primeiro como criamos um grupo:

meu_grupo group - - i=100,c=0
meu_serveridor_1 onsoctcp meu_hostname_1 1526 k=1,g=meu_grupo
meu_serveridor_2 onsoctcp meu_hostname_2 1526 k=1,g=meu_grupo

Aqui estamos a criar um grupo com o nome "meu_grupo", definido porque usei "group" no campo do protocolo. Não utilizei o nome do servidor (hostname) nem o nome de serviço ou porto (sendo ambos substituídos por "-"), e usei duas opções:
  • i=100
    Apenas define um ID de grupo. Pode ser qualquer número (não repetido noutros grupos)
  • c=0
    Isto controla a forma como os clientes que acedem ao INFORMIXSERVER=meu_grupo lidam com os vários servidores do grupo. Esta opção pode ser definida a "0" ou "1":
    • 0 significa que tentam sempre ligar-se ao primeiro servidor do grupo, e se esse não responder passam ao seguinte e assim sucessivamente
    • 1 significa que irão aleatoriamente escolher um servidor para iniciar as tentativas de ligação e se não estiver a responder passam ao seguinte e assim sucessivamente
Como é que isto é útil? Vou dar dois cenários de utilização. O primeiro é um cluster com um servidor primário, um servidor HDR e outro RSS. Ambos os secundários estão em modo de leitura apenas e não planeamos mudar os papéis de cada um. É natural que o HDR esteja mais sincronizado com o primário que o RSS. Neste caso podemos desejar dirigir clientes de leitura preferencialmente para o nó HDR, mas em caso de falha ou manutenção planeada deverão ir para o RSS. As seguintes entradas do INFORMIXSQLHOSTS conseguiriam isto:

meu_grupo_leitura group - - i=100,c=0
meu_no_hdr onsoctcp meu_host_hdrt 1526 k=1,g=meu_grupo_leitura
meu_no_rss onsoctcp meu_host_rss 1526 k=1,g=meu_grupo_leitura

Depois basta usar INFORMIXSERVER=meu_grupo_leitura. Os clientes irão tentar ligar-se à instância "meu_no_hdr", mas se isso falhar vão tentar a conexão ao "meu_no_rss". E pronto... O redirecionamento de clientes mais simples que pode existir. Isto tem vantagens e desvantagens:
  • Não precisa de mais nenhum software (como o connection manager). É extremamente simples de configurar
  • Trabalha com clientes com versões muito antigas, que não saibam lidar com o modo REDIRECT do connection manager.(espero poder explicar isto num próximo artigo em que cubra o connection manager)
  • Não funcionará devidamente se trocarmos os papéis das instâncias (como primário para secundário e vice-versa)
  • Apesar de podermos definir "c=1" para distribuir aleatoriamente as conexões por ambos os servidores, os modos mais avançados de balanceamento só estão disponíveis no connection manager
Vejamos agora o segundo cenário. Como vimos acima, o connection manager permite mais flexibilidade. Mas sabemos que uma vez implementado, passa a ser um ponto de falha. Se estiver em baixo não será possível estabelecer ligações aos servidores, ainda que esteja tudo bem com eles. Naturalmente a solução para isto será ter dois (ou mais) connection managers a atuar em conjunto em máquinas diferentes. Mas depois, como configurar os clientes? Bom, de forma muito semelhante ao exemplo anterior. Mas desta vez, em vez dos listeners dos servidores teremos dois listeners (um em cada máquina que corra os connection managers), e queremos que os clientes vejam ambos e se tentem ligar através de ambos. Assim, assumindo que criamos os serviços (SLAs) cm_1 e cm_2 nos dois nós que correm o connection manager, ambos estarão configurados para redirecionar para os nós secundários. O INFORMIXSQLHOSTS do lado do cliente seria:

meu_grupo_leitura - - i=100,c=1
cm_1 onsoctcp cm1_hostname 1526 g=meu_grupo_leitura
cm_2 onsoctcp cm2_hostname 1526 g=meu_grupo_leitura

Portanto, os nossos clientes seriam configurados com INFORMIXSERVER=meu_grupo_leitura e tentariam ligar-se a um dos connection managers de forma aleatória. Depois de alcançarem um dos serviços cm_* seriam redirecionados para o nós secundário apropriado. A configuração dos connection managers não será abordada neste artigo.

Existe apenas mais um detalhe que merece referência. Por omissão os clientes Informix esperam muito tempo (60 segundos) até completarem uma tentativa de conexão. Havendo redirecionamento de conexões provavelmente não queremos esperar tanto tempo... Na verdade, se um servidor não responder em digamos, 10 segundos, provavelmente já não vai responder (e estou a acomodar algum tempo para problemas de DNS por exemplo). Podemos definir duas variáveis de ambiente para configurar este comportamento:
  • INFORMIXCONTIME
    O timeout que o cliente espera pelo fim do estabelecimento da conexão (em segundos)
  • INFORMIXCONRETRY
    O número de tentativas que o cliente faz dentro do tempo definido por INFORMIXCONTIME

Wednesday, July 31, 2013

My feature wish list / A minha lista de funcionalidades que faltam

This article is written in English and Portuguese
Este artigo está escrito em Inglês e Português

English version

Disclaimer
Again, although I have a clear disclaimer on the side, the following does not represent my employer views and in no way is related to any current plans or roadmaps. In fact this is probably the post where I'm closer to the customers than ever.

Why I'm writing this?
I started this article even before v12.10 was out. Due to some constraints I kept the article in draft for a few months. Now is a great time to publish it. The reason for this is that IBM has done an extraordinary thing (not so recently...): It created and made available a platform where customers can make requests for enhancements in some (the list is increasing) of the IBM portfolio products. And guess what? Informix is already there.
I must admit that I have great expectations and I'm pretty enthusiastic about this initiative, for a number of reasons:

  • It's a direct link between customers and IBM. Tell IBM exactly what you need and why... No messing around with sales reps that have other priorities, no PMRs, no user groups filtering
  • Being a Web platform it's easy and very quick to use. It allows insertion, voting, subscribing to updates, commenting and so on... The list of features is fantastic
  • Means IBM is willing to take some risks: If a feature is highly requested/voted and seems easy to implement, it will be harder to explain why it's not implemented (keep in mind that resources are always insufficient...)
  • It's public... again this is a risk for IBM.
All this brings some drawbacks:
  • If IBM takes requests seriously (and naturally I believe and hope it will), it may interfere with a more integrated and planned approach to deliver new features
  • There will be many requests which are badly explained, or that aren't clear enough, or that simply can be done in other ways. These will consume resources to filter and may possibly obfuscate the really interesting (note the subjectivity in this sentence) features
  • It may expose product limitations to competitors (again I think this a very courageous initiative)
Having explained all this, the link to the site was added to the list on the side of this blog. At the time of this writing there are already 74 requests for Informix. I must admit I've added some. The success of this process will depend on customer participation. Review the requests, select the ones you like, vote for them, comment as you think appropriate and create a "watch list" so that you're notified of changes. And of course, add your own if the features you dream about aren't' already there.
I think of this article as a public reflection about functionality I feel is missing currently in Informix. This comes from several customer engagements but are not easy to put into formal feature requests, essentially because those would require a proper business case. So, this is my personal view and feelings, but these are of course influenced by my customer assignments. Meanwhile I'll try to stick with what seems to me as feasible technical features. I'm leaving out stuff like "I'd like Informix to be the market leader", "I'd like to see application provider XPTO supporting it" etc.

Naturally, since I don't work in product development, my thoughts about some of these features feasibility are merely speculation. I'm not able to quantify them in terms of man hours needed to implement and support them. So take all this more like a brain storm. And if you like some of them, vote for them in the site, push for them however you can (IIUG meetings, customer advisory boards, PMRs, through sales representatives, during beta testing programs etc.) Bare in mind that the good people in R&D surely have a lot on their minds, but not necessarily everything we want or need. And of course, since resources are scarce, they must establish priorities. For that they must receive feedback from the field.
For each feature I'll try to give my idea on how it could be implemented and if this would involve a big development effort (with the limitations stated above). Note that the order presented does not represent a priority order

My personal wish list:
  1. Have the engine automatically "upgrade" the statistics during/after conversion/upgrade
    • Description
      We've always had the easiest upgrades in the marketplace. And CONVERSION_GUARD added an unprecedented confidence level to it. But we still require UPDATE STATISTICS to be run after the conversion. Doing it for the procedures is usually very fast. But for all the tables can take a long time (an experienced DBA knows several ways to speed them). I would like to see the engine "upgrade" if needed the existing statistics. This should be done only when the statistics format change (which I believe is rare). If the data collected in the new version doesn't exist in the old version, than the engine should work without them. The purpose of this would be to be able to open the database for the applications as soon as the conversion ends (typically after a few minutes only).
    • How to do it
      I really don't know. To be honest I really don't know if this is really necessary. How often do the statistics format change? Not many I suppose. The idea of this feature is to make this a supported mechanism that would not require full statistics recalculation. Naturally if the new version gathers more info, the optimizer could possibly miss some info required to achieve the best query plan (but should at least achieve the best ones from previous version)
    • Benefit
      Reduce the time for doing a supported upgrade into a few minutes. I know...we can do upgrades of MACH-11 clusters with no downtime, but the way we do that is complex (not from the user perspective, but from the engine perspective). And not everybody uses MACH-11
  2. Allow setting up MACH-11 secondary nodes on heterogeneous platforms
    • Description
      Currently we can only have HDR/RSS nodes on the same platform as the primary server. There are good reasons for this and I probably don't know all of them. But some of these are:
      • We need a "bit by bit" image on the secondary nodes. So, platform Endianess becomes an issue
      • We need exactly the same layout, so different page sizes becomes an issue
      • Even if IBM could fix both issues above, there would still be problems for 3rd party extensions (datablades) than implement their own datatypes and operations
    • How to do it
      This is the point where my technical ignorance on several implementation aspects raises the risk of saying something really stupid. But I think the first two issues above could be solved. Regarding the endianess, we should take that into account when sending information between the nodes. I believe this is already done for several aspects of the engine (Enterprise replication, distributed queries etc.). With that in mind, I think it would be possible to transform the information, so that although physically different (byte order could change), the "bit by bit image" would be established at a level above, in the Informix low level access mechanisms (reading/writing to disk). Of course this would have to be implemented in every aspect of the engine used to establish and run HDR/RSS nodes (log shipping, backup/restore, ifxclone etc.)
      Regarding the page size, this is a limitation, but in reality there are only two default page sizes: 4KB used in AIX and Windows, and 2KB used in all the rest (which are not many - HP-UX, Solaris, Linux....?). Why not let the user define the default page size when creating an instance? Assuming he defines 4KB, it would be acceptable in every platform. So the user would have to consider this before creating the instance.
      The last issue, which relates to 3rd party extensions, I'd assume that as a limitation. If the 3rd party implements the needed mechanisms to take the other issues into account, than fine, it would be supported. Otherwise it wouldn't. My point is that those 3rd party products are not widely used
    • Benefits
      I think it's easy to explain... Currently a platform change requires some export/import method, enterprise replication or distributed queries to move the data. With the possibility of having HDR/RSS nodes on different platforms this would be achievable in the easiest manner (there is nothing easier to setup than HDR) and with no down time at all
  3. Allow upgrades of secondary servers without having to restore from the primary
    • Description
      Currently the only supported way to upgrade a secondary server (HDR or RSS) from version A to B, is to install version B and restore from the primary. In some scenarios, when the servers are close together, this can be achievable in very reasonable time. But for geographically distant servers, this is a real problem. To give you an example we have a customer with a 6TB database... It takes a very large amount of time to take those 6TB from place A to place B. If for a primary server you can do the upgrade in a few minutes, for a secondary server it can be a nightmare
    • How to do it
      This would be harder than what I'll explain here, but I believe it would be feasible and it would be a great feature for customers. First, the primary would need to be upgradable without loosing awareness of the secondary servers. Then, assuming we're upgrading a sever which is a primary server in a MACH-11 cluster, it should be able to save all the new page images (of changed pages during the conversion) in the file system. Very similar to what we do in the CONVERSION_GUARD feature. But instead of the original image, we should store the new image. After this, we should start the secondary with the new version. Once it started it should notice that it's not running the previous version. As such it should ask the primary if it's on the same new version. If it is, it should ask if the primary has the "changed pages stored". If yes, it should accept the transfer of those pages and store them on the local storage. After that it should continue working as before. I can spot a problematic point... This would probably not work if the nodes were not synchronized before the primary conversion started... So some restrictions or mandatory steps could be implemented
    • Benefit
      Allow upgrades in MACH-11 clusters without the need to re-transmit the whole instance image to the secondary locations
  4. Expanding the audit trail format
    • Description
      I've already made some requests in order to get more information in the audit logs. But R&D has problems because that would break compatibility. I appreciate this concern, but it really limits the ability to use the audit system
    • How to do it
      Have some sort of mechanism that would allow the DBA to define what he wants in the audit trail. The easiest way would be to have some form of audit log versioning and have the DBA choose the version he wants (default could be the "classic"). The hardest one, but much more flexible would be to allow the DBA (better saying the DBSSO) to specify what info he wanted for every action mnemonic. This could be defined based on a predefined default that could be changed in some graphical tool (OAT?). The result could be an XML file with the specification. For example, for RDRW, INRW, DLRW and UPRW the user could eventually specify the full row. I would be happy with anything between the easiest (pre-defined formats that could change with versions) and full customization for each mnemonic
    • Benefit
      Allow better usage and increase acceptance of the audit system
  5. Trusted context should allow "remote" operations
    • Description
      Trusted context is a nice feature, and 4GL web services should be able to take advantage of it. But when you're inside a trusted context and you change user identity, you're not allowed to execute remote operations. This happens because of a very good reason (security). The new identity may have been granted some privileges on a remote database, and you, as a privilege local user could create a trusted context, change the identity, and by doing so, abuse the remote privileges granted to that user. So why am I complaining? Because in real life, many of our customers have created several local databases (in the same instance) and their applications require the users to be able to run distributed (local to the instance) queries. So effectively many of them cannot use trusted contexts
    • How to do it
      Allow a local DBSECADM to relax the security, allowing the users to do "remote" operations, that are local to the instance. This could be achievable through a new parameter (global to the instance) or a database option (configurable through SQL)
    • Benefit
      Turn trusted context feature into something really usable by the majority of customers. Currently I believe it isn't, because many of them want to re-use 4GL business rules inside web services, and their databases require the connection to use the end user identity (because they implemented trigger based logging etc.). Using the end user identity in web services would work better on a trusted context to avoid contant connection establishement
  6. Be able to flush any of the internal caches
    • Description
      We can flush the statement cache, but we cannot flush the distribution cache, the dictionary cache etc. Although it's true we should not need this, I've faced two bugs and an unknown situation where I needed to flush some of these (procedure, distribution and dictionary cache). The bugs were reported and fixed. But as a workaround it would have been nice to have this
    • How to do it
      Implement some new onmode options, and/or SQL API. It could simply flush the cache (simpler to implement I suppose, but with serious impact), or allow us to remove a certain entry (harder to implement I suppose, but with less impact on a running instance)
    • Benefit
      Avoid some problems typically caused by bugs. On the absence of this the other options we have are stopping the instance or try to saturate the cache so that the entry in question is kicked out. This is usually very hard to achieve although it's possible.
  7. Be able to flush the resolver cached entries
    • This was extensively explained in a previous post. The problem summary is that currently we don't have a way to change the DNS servers without stopping the instance.
    • How to do it
      Force all MSC VPs to call the OS function res_init()
    • Benefit
      Avoid down times in case of DNS infra-structure changes
  8. Eliminate the restrictions on DRDA
    • Description
      Currently we have two protocols to talk to Informix. SQLI and DRDA. DRDA is the protocol used by the common client and allows connections to DB2, Informix and other DBs. Unfortunately DRDA still has some limitations like some specific Informix datatypes etc. This may prevent it's general adoption by customers. And since many IBM products are using DRDA to talk to DB2, this could make it easier to get those products working with Informix (the ones which still don't)
    • How to do it
      This is a complex task. It could eventually require changes in the DRDA protocol. So, this is really a "wish"...
    • Benefit
      Take advantage of some functionality of DRDA not present in SQLI. Allow more easy portability of some products to work with Informix
  9. Extend FORCE_DDL_EXEC to all Informix DDL
    • Description
      In 11.50 we introduced this environment setting to force an ATTACH/DETACH FRAGMENT, meaning any other transaction preventing it will be aborted. Any DBA on a complex system immediately started to cry for this in every DDL statement like ALTER TABLE, CREATE/DROP TRIGGER etc.
    • How to do it
      Extend the current implementation to all the other scenarios. I really have no idea of the work involved and the implications this might have
    • Benefit
      Make it easier running DDL on a busy system. Typically DBA spend more time trying to free some table than to apply the change itself
  10. Parallel scans on non fragmented partitions
    • Description
      We only launch one scan thread per partition, because we assume it's located in a disk and we don't want the disk heads jumping around like crazy. But this is currently outdated because of the changes in the concept of "disk" (with the SANs and the stripes etc.). This can slow us down in many situations, when we could take advantage of the parallelism embedded in the engine 
    • How to do it
      We could link this to IMPLICIT_PDQ, or create some other form of control (if PDQPRIORITY is set and the table is larger than X etc.). Once we calculate the correct parallelism, the single partition would be virtually split into smaller pieces and each one should receive it's scan thread
    • Benefit
      Much faster queries on non partitioned tables (if PDQ or IMPLICIT_PDQ) were set
  11. Be able to get the query plan in simple text in the session sending the query (just a simple view)
    • Description
      Currently we need to write the query plan to a local database filesystem, or to use a tool that can show the visual explain. I'd like to see functionality inside the engine that would convert the XML file generated by the EXPLAIN_SQL() function into simple text and make it available in a session view
    • How to do it
      I've created a proof of concept and presented it in 2010 IIUG conference. But due to lack of time and some technical issues I could not pursue this. Later I worked on a very usable (I'm biased obviously) alternative. But it still is a workaround
    • Benefits
      Obtaining a query plan should be a trivial task for any user connecting to the database. Currently it isn't.
  12. Be able to get the query plan of a running query
    • Description
      The DBA should be able to easily obtain the query plan of a running query. This is more or less possible (xtree), but the result is less than satisfactory. Other options would need to have some features activated (SQL Trace)
    • How to do it
      Implement some sort of EXPLAIN_SQL() that instead of a query text receives a session ID, or other simple way to specify the running query. It should/could also be associated with onstat -g pqs (that although sometimes useful was clearly not done for the DBA - I assume it was designed as a support function). For older guys, xtree almost does this, but the output is not clear enough and it requires x-windows
    • Benefit
      Increase DBA productivity. We should be able to easily understand why a current session is slow. Currently it's a bit of guess work
  13. Implement session limits and priorities
    • Description
      We should be able to limit several session limits (memory, locks, logical log space, cpu usage etc.). Some of this are already implemented, some have been talked about and others seem to be relatively easy. Take CPU usage for example... We already have threads, and there are different priorities for them. Just create an interval for normal user threads and let the DBA change it dynamically for a running query/session
    • How to do it
      Part of it was already done, others are probably underway. The CPU usage is described above
    • Benefit
      Improve the DBA ability to control the system in order to provide the best performance and highest system availability
     
So, some of these were added by me to the request for enhancements (RFE) site. If you like them, vote for them. Comment as you like of course. And if you have more, please report them in the tool.
Now is the time for the community to stand up and lobby for the features it need. Keep in mind that a request not clearly explained will not have great success. Feel free to discuss it with colleagues, other users in the forums or user group meetings etc.


Versão Portuguesa

Desresponsabilização
Ainda que tenha um termo de desresponsabilização claro na lateral do blog, quero reforçar que este artigo (tal como todos os outros) não representa a posição do meu empregador e não está relacionado com quaisquer planos ou roadmaps. Na verdade, este é provavelmente o artigo em que estou mais próximo dos clientes.

Porque é que estou a escrever isto?
Iniciei a escrita deste artigo ainda antes do lançamento da versão 12.10. Devido a constrangimentos vários mantive o artigo em rascunho, por vários meses. Mas agora é uma boa altura para o publicar. A razão é que a IBM fez algo de extraordinário (já há algum tempo). Criou e disponibilizou uma plataforma onde os clientes podem efetuar pedidos de melhoria nos produtos (sendo que a lista está a crescer) do portfolio IBM. E a boa notícia é que o Informix já lá está.
Devo admitir que tenho grandes expectativas e sou muito entusiasta desta iniciativa, por um número de razões:

  • É uma ligação direta entre os clientes e a IBM. Diga à IBM exatamente o que necessita e porquê... Nada de encaminhar os pedidos através de representantes de vendas que têm outras prioridades, ou através de PMRs ou grupos de utilizadores.
  • Sendo uma plataforma web é fácil e rápido de usar. Permite inserção de pedidos, votar em pedidos, subscrever alterações, comentar etc.... A lista de funcionalidades é fantástica
  • Implica que a IBM esteja disposta a correr alguns riscos: Se um pedido tiver muitos votos, e parecer fácil de implementar, será mais difícil à IBM justificar porque não o faz (convém no entanto não esquecer que os recursos são sempre escassos e limitados)
  • É público... novamente, isto é um risco acrescido para a IBM.
E também pode ter algumas desvantagens:
  • Se a IBM tomar estes pedidos seriamente (e naturalmente eu acredito e espero que o faça), isto pode interferir com uma abordagem mais planeada e integrada para desenvolvimento dos produtos e disponibilização de funcionalidades
  • Existirão muitos pedidos que serão mal explicados ou não serão suficientemente claros, ou que poderão ser alcançados com funcionalidades já existentes. Isto consumirá recursos para filtrar e poderá ofuscar as funcionalidades realmente interessantes (claro que isto é subjetivo...)
  • Pode ajudar a expor limitações dos produtos à concorrência (também por isto me parece uma iniciativa corajosa da IBM)
Tendo explicado tudo isto, o link para o site foi adicionado à lista na lateral deste blog. No momento em que estou a escrever este artigo já existem 74 pedidos para Informix. Devo admitir que eu próprio inseri alguns. O sucesso deste processo dependerá em absoluto da participação dos clientes. Reveja os pedidos, selecione os que lhe agradam, vote neles, comente conforme entenda apropriado e crie uma "watch list" de forma a que seja notificado de qualquer alteração. E claro, adicione os seus próprios pedidos se verificar que ainda não estão registados.
Considero este artigo como uma reflexão pública sobre funcionalidades que sinto fazerem falta no Informix. Isto deriva de muitas situações com clientes, mas não é fácil de colocar numa feature request formal, essencialmente porque requereria um business case devidamente fundamentado. Assim isto é a minha visão e desejo pessoal, mas naturalmente influenciado pelos projetos em clientes.
Entretanto, tentei limitar-se ao que me parece ser razoável e de implementação prática "possível" dentro das funcionalidades técnicas. Naturalmente deixei de fora coisas como "desejo que o Informix seja o líder de mercado" ou "gostaria de ver a aplicação XPTO suportar o Informix" etc.
Convém também dizer que dado que não trabalho no desenvolvimento de produto, a minha opinião sobre a dificuldade de implementação de determinadas funcionalidades é mera especulação. Não sou capaz de as quantificar em termos de horas/homem necessárias à sua implementação e suporte. Assim, assuma que isto é mais um brain storm. Se gostar de alguma delas, vote no referido site, faça pressão por elas onde lhe for possível (reuniões do IIUG, customer advisor boards, PMRs, representantes de vendas, durante os EVP - early validation programs - etc.). Tenha presente que a equipa de I&D já terá muito em mente, mas não necessariamente tudo o que queremos ou precisamos. E claro, dado que os recursos são escassos, têm de estabelecer prioridades. Para tal necessitam de receber feedback dos clientes
Para cada funcionalidade tentei dar a minha ideia de como poderia ser implementada e se tal implicaria um grande esforço de desenvolvimentos (com as minhas limitações indicadas acima). Note que a ordem de apresentação é completamente aleatória e não reflete a minha opinião sobre prioridades

A minha lista de desejos pessoais


  1. Fazer com que o motor "actualize" automaticamente as distribuições/estatísticas durante/após uma conversão/iupgrade
    • Descrição
      Sempre tivemos o processo de upgrade mais fácil do mercado. E a introdução do CONVERSION_GUARD adicionou-lhe um nível de confiança sem precedentes. Mas ainda é necessário correr os UPDATE STATISTICS após a conversão. Fazê-lo para os procedimentos é geralmente muito rápido, mas fazê-lo para todas as tabelas pode levar muito tempo (um DBA experiente conhece várias formas de as acelerar). Gostaria de ver o motor alterar, se necessário, as estatísticas existentes. Isto deveria ser feito apenas quando o formato das mesmas mudar (o que me parece raro). Se houver detalhes recolhidos na nova versão que não eram recolhidos na anterior, então o motor/optimizador deveria saber trabalhar sem eles. Isto permitiria abrir a base de dados ás aplicações muito mais rápido, logo após o fim da conversão (que demora normalmente uns poucos minutos)
    • Como fazer
      Honestamente não sei. Na realidade não sei se isto é realmente necessário. Quão frequentemente mudamos o formato das estatísticas/distribuições? Não muito, julgo eu. A ideia desta funcionalidade seria não necessitar da total re-criação das estatísticas/distribuições. Logicamente se a nova versão recolher mais detalhes, o optimizador poderia não ter toda a informação para escolher o melhor plano possível, mas deveria manter o melhor plano da versão anterior
    • Benefício
      Reduzir o tempo necessário a fazer uma conversão de forma suportada a apenas alguns minutos. Eu sei... podemos fazer upgrades em clusters MACH-11 sem downtime, mas a forma como isso é feito é complexa (não do ponto de vista do utilizador, mas sim do motor). E nem toda a gente utiliza MACH-11
  2. Permitir montar nós secundários de MACH-11 em plataformas heterogéneas
    • Descrição
      Atualmente só podemos ter nós HDR/RSS na mesma plataforma que o servidor primário.
      Existem boas razões para isto e provavelmente não as sei todas. Mas algumas destas são:
      • Necessitamos de uma imagem "bit por bit" nos nós secundários. Como tal a questão da Endianess das plataformas é relevante
      • Necessitamos exatamente da mesma disposição de páginas, portanto a questão do tamanho das páginas pode tornar-se um problema
      • Mesmo que a IBM "consertasse" os dois problemas anteriores, ainda haveria problemas potenciais com extensões de terceiros (datablades) que implementem os seus próprios tipos de dados e operações
    • Como fazer
      Este é o ponto onde a minha ignorância técnica sobre diversos aspetos da implementação aumenta o risco de escrever algo realmente estúpido. Apesar disso, penso que os dois primeiros problemas acima poderiam ser resolvidos. Em relação à endianess, teríamos de ter isso em conta no envio e receção de informação entre os nós. Penso que isto já é feito em diversos componentes do motor (Enterprise Replication, queries distribuídas etc.). Com isto em mente, penso que seria possível transformar os dados, de forma a que apesar de fisicamente diferente (a ordem dos bytes poderá ser diferente), a imagem "bit por bit" seria alcançada num plano superior, nas camadas baixas de acesso do motor Informix. Claro que isto teria de ser implementado ao nível de todos os componentes usados na gestão do MACH-11 (envio de logical logs, backup/restore, ifxclone, etc.)
      Sobre o problema dos tamanhos de página, isto é uma limitação, mas a realidade é que só existem dois tamanhos: 4KB usado em AIX e Windows, e 2KB usados em todos os outros (que são cada vez menos - HP-UX, Solaris, Linux... ?). Porque não deixar o utilizador definir o tamanho de página "de sistema" ao criar uma instância? Assumindo que o utilizador escolhesse 4KB, seria "portável" em qualquer plataforma. Assim o utilizador teria de considerar isto durante a criação da instância.
      O último problema refere-se a extensões de terceiros. Eu assumiria isto como uma limitação. Se o fornecedor implementasse os mecanismos para tornar isto possível, então ótimo, seria suportado. Caso contrário não seria. O meu ponto é que a maioria dos clientes não utiliza estas extensões de terceiros.
    • Benefício
      Esta parte penso que é muito fácil de explicar. Atualmente uma mudança de plataforma requer algum trabalho de export/import, definição de Enterprise Replication ou queries distribuídas para mover os dados. Com esta possibilidade de ter nós HDR/RSS em diferentes plataformas, isto seria alcançado da forma mais fácil (e não há nada mais fácil que estabelecer um nó HDR), e virtualmente sem nenhum donwtime.
  3. Permitir upgrades de servidores secundários sem ter de restaurar a partir do primário
    • Descrição
      Atualmente a única forma suportada para efetuar um upgrade de um nó secundário (HDR ou RSS) da versão A para a B, é instalar a versão B e restaurar a partir do primário (que entretanto será migrado). Em alguns cenários, quando os servidores estão juntos, isto pode ser realizado num tempo razoável. Mas para servidores geograficamente distantes, isto é um problema bem real. Para ilustrar posso dar um exemplo de um cliente com uma base de dados de 6TB... Isto demora um tempo muito razoável a transferir do local 1 para o local 2. Se para um servidor primário se pode fazer um upgrade em alguns minutos, para um secundário isto pode ser um pesadelo
    • Como fazer
      Isto será mais difícil do que vou fazer parecer, mas acredito que seja possível de implementar e traria um grande benefício aos clientes. Antes de mais o primário teria de poder sofrer o upgrade sem perder a noção que é um primário. Assim, durante o upgrade de um servidor primário de um cluster MACH-11, o servidor deveria ser capaz de gravar em filesystem todas as novas imagens das páginas alteradas. Muito semelhante ao que fazemos com a funcionalidade do CONVERSION_GUARD. Mas ao invés de guardar as imagens anteriores, deveríamos gravar as imagens novas.
      Depois  disso, deveríamos arrancar com o servidor secundário na nova versão. Ao iniciar deveria notar que não está a executar a versão anterior. E assim, deveria contactar o servidor primário e "perguntar" se estão na mesma versão. Se sim, deveria perguntar se o primário tem as páginas alteradas gravadas em disco. Se sim, deveria aceitar a transferência dessas páginas e aplicá-las ao storage local. Após isso era como se a conversão tivesse ocorrido também no secundário. Provavelmente seria necessário parar e arrancar de novo, após o que deveria trabalhar normalmente. Penso que o único ponto a ter em conta é que o procedimento não iria funcionar se os nós não estivessem absolutamente sincronizados antes do procedimento. Mas isso faria parte das restrições ou passos mandatórios para efetuar o procedimento.
    • Benefício
      Permitir upgrades de nós secundários de clusters MACH-11 sem a necessidade de re-transmitir a imagem completa da instância para as localizações secundárias
  4. Expandir o formato do audit trail
    • Descrição
      Já efetuei alguns pedidos para introduzir mais informação nos logs de audit. Mas o departamento de I&D tem algumas reservas pois isso quebraria a compatibilidade com possíveis scripts criados pelos clientes. Valorizo esta preocupação, mas penso que isto de facto limita muito a utilidade do sistema de audit 
    • Como fazer
      Poderíamos ter algum mecanismo que permitisse ao utilizador definir o que quer que apareça no audit trail. A forma mais fácil seria ter diferentes versões do formato do audit log e deixar que o utilizador escolhesse a versão que quer usar (por omissão seria o formato "clássico"). A mais difícil, mas muito mais flexível, seria deixar o DBA (ou melhor o DBSSO) especificar que informação deveria aparecer para cada menemónica (ou operação auditável). Isto poderia ser definido a partir de um template numa qualquer ferramenta (OAT?). O resultado poderia ser um ficheiro XML com a nova especificação que fosse depois "aplicado" ao motor. Por exemplo, para RDRW, INRW, DLRW e UPRW o utilizador poderia querer ver a linha de dados completa. Eu ficaria satisfeito com qualquer solução intermédia entre estas duas apresentadas.
    • Benefícios
      Permitir maior utilidade e promover a aceitação do sub-sistema de audit
  5. Os trusted contexts deveriam permitir operações "remotas"
    • Descrição
      A introdução da funcionalidade Trusted Context foi interessante, e os 4GL web services deveriam poder tirar proveito disso. Mas quando estamos dentro de um trusted context e mudamos a identidade do utilizador, não podemos executar operações remotas. Existe uma boa razão para isto acontecer: segurança. A nova identidade pode possuir alguns privilégios numa base de dados remota, e nós, como utilizadores locais privilegiados (DBA) poderia-mos criar um trusted context, mudar a identidade, e assim abusar dos privilégios remotos concedidos a esse utilizador. Assim, porque me estou a queixar? Porque no mundo real, muitos clientes criaram várias bases de dados locais (na mesma instância) e as suas aplicações requerem que os utilizadores possam fazer queries distribuídas (locais á instância). Assim, na prática, muitos deles não podem usar trusted contexts 
    • Como fazer
      Permitir que um DBSECADM local possa "relaxar" a segurança, permitindo que operações "remotas", que sejam locais à instância possam ser executadas. Isto poderia ser feito através de um parâmetro (global à instância) ou ao nível da base de dados (configurável por SQL)
    • Benefício
      Tornar os trusted contexts algo realmente útil e utilizável por clientes que estejam a reaproveitar código 4GL dentro de web services por exemplo. Nestes contextos um dos obstáculos é que o ambiente depende de logging (triggers) dentro da BD que por sua vez só funcionam devidamente com a identidade do utilizador final (o que não é hábito num contexto de web services)
  6. Conseguir limpar algumas das caches internas
    • Descrição
      Nós conseguimos limpar a statement cache, mas não outras como a distribution cache, a dictionary cache etc. Embora seja verdade que isto não deve ser necessário, já encontrei dois bugs e uma situação não clarificada, onde isto foi necessário. Os bugs foram naturalmente reportados e corrigidos. Mas como workaround teria sido bom conseguir limpar algumas destas caches. 
    • Como fazer
      Implementar algumas novas opções no onmode e/ou na SQL API. Poderia simplesmente limpar a cache (mais simples de implementar talvez, mas com maior impacto), ou permitir-nos eliminar uma entrada específica da cache (mais difícil, mas com menor impacto durante a execução da instância)
    • Benefício
      Evitar alguns problemas tipicamente causados por bugs. Na ausência desta possibilidade as alternativas são parar a instância ou tentar saturar a cache de forma a que a entrada indesejada seja "empurrada" para fora da cache. Isto é normalmente muito difícil de fazer (embora seja possível)
  7. Ser possível limpar a informação em cache do sistema que faz a resolução de nomes e de IPs
    • Descrição
      Este problema foi explicado extensivamente num artigo anterior. O sumário do problema é que atualmente não conseguimos fazer alterações na infra-estrutura de DNS sem parar a instância
    • Como fazer
      Forçar todos os VPs da classe MSC chamar a função res_init()
    • Benefício
      Evitar downtime em caso de alterações na infra-estrutura de DNS
  8. Eliminar as restrições do DRDA
    • Descrição
      Atualmente temos dois protocolos de comunicação com o Informix. SQLI e DRDA. DRDA é o protocolo usado pelo common client que permite ligações a Informix, DB2 e outras BDs. Infelizmente os clientes DRDA ainda têm algumas limitações como acesso a alguns tipos de dados específicos do Informix etc. Isto pode prevenir a sua adoção genérica pelos clientes. Como muitos produtos IBM usam DRDA para falar com o DB2, isto poderia tornar mais fácil suportar o Informix (no caso dos produtos que ainda não suportam)
    • Como fazer
      Isto é uma tarefa complexa. Poderá ser necessário efetuar alterações ao protocolo DRDA. Assim sendo é mais um "desejo"...
    • Beneficio
      Tirar proveito de algumas funcionalidades do DRDA que não estão presentes no SQLI. Facilitar que outros produtos IBM suportem o Informix
  9. Estender o FORCE_DDL_EXEC a todos os tipos de DDL
    • Descrição
      Na versão 11.50 introduzimos uma variável de ambiente que força o fecho de transações que estejam a impedir  um ATTACH/DETACH FRAGMENT. Qualquer DBA num ambiente complexo gritou para que isto existisse para qualquer tipo de DDL (ALTER TABLE, CREATE/DROP TRIGGER etc.)
    • Como fazer
      Estender a atual implementação a todos os outros cenários. Na realidade não faço ideia do esforço envolvido e das implicações
    • Benefício
      Tornar mais fácil executar DDL num sistema muito ativo. Normalmente os DBAs passam mais tempo a tentar libertar uma tabela do que a aplicar a alteração propriamente dita
  10. Scans paralelos em tabelas não fragmentadas
    • Descrição
      Apenas lançamos uma thread por partição, porque tradicionalmente assumimos que está localizada num disco e não queremos que as cabeças do disco andem a saltar de um sitio para outro. Mas isto está naturalmente desatualizado devido às alterações sofridas ao nível de storage. O conceito de "disco" já não é o que era (com SANs, stripes etc.).
      Este facto pode tornar os scans desnecessariamente lentos em muitas situações em que de outra forma poderíamos tirar proveito dos mecanismos de paralelismo existentes no motor
    • Como fazer
      Poderíamos ligar isto ao IMPLICIT_PDQ, ou criar outra forma de controlo (se o PDQPRIORITY está ativo e a tabela é maior que X etc.). Uma vez calculado o nível correto de paralelismo, uma única partição poderia ser virtualmente considerada como várias e lançada uma scan thread para cada parte
    • Benefício
      Queries muito mais rápidas em tabelas não particionadas (se PDQPRIORITY ou IMPLICIT_PDQ estiverem activos)
  11. Possibilitar que o plano de execução de queries seja obtido em texto simples a partir da própria sessão (usando uma view por exemplo)
    • Descrição
      Atualmente necessitamos de escrever o plano de execução em filesystem, ou usar uma ferramenta que suporte o visual explain. Seria bom ter funcionalidade dentro do motor que convertesse o XML gerado pela função EXPLAIN_SQL() em simples texto e que o disponibilizasse numa view.
    • Como fazer
      Criei uma prova de conceito e apresentei o resultado na conferência do IIUG em 2010. Dada a falta de tempo e alguns problemas técnicos não consegui prosseguir o trabalho. Mas recentemente trabalhei numa alternativ bastante mais fácil (claro que sou suspeito). Mas ainda assim é um workaround
    • Benefício
      Obter um plano de execução tem de ser algo trivial e simples para qualquer utilizador que se conecte à base de dados. Atualmente não é.
  12. Poder obter o plano de execução de uma query que já está em execução
    • Descrição
      O DBA deveria poder obter de forma rápida e fácil o plano de execução de qualquer query que se encontre em execução. Existe uma ferramenta que de certa forma o permite fazer (xtree), mas o resultado é menos que satisfatório. Alternativamente haveria que ter algumas funcionalidades ativadas (SQL Trace)
    • Como fazer
      Implementar algo semelhante ao EXPLAIN_SQL() mas que em vez do texto SQL recebesse um ID de sessão ou outra forma simples de especificar a query em execução que nos interessa. Seria interessante se pudesse estar ligado ao onstat -g pqs (que embora possa ser útil em algumas situações, claramente não foi feito a pensar no DBA - suponho que tenha sido desenhado como ferramenta para o suporte).
    • Benefício
      Aumentar a produtividade do DBA. Perceber a razão de lentidão identificada numa sessão tem de ser fácil e simples. Atualmente requer dotes de adivinhação
  13. Implementar limites e prioridades para sessões
    • Descrição
      Deveria ser possível limitar vários recursos por sessão (memória, locks, espaço em logical logs, uso de cpu etc.). Alguns destes já estão implementados, outros foram falados e outros ainda parecem relativamente fáceis de fazer. Considere-se o uso de CPU por exemplo... já temos threads e há várias prioridades para as mesmas. Aparentemente bastaria criar um intervalo de prioridades para sessões de utilizador e deixar o DBA geri-las ou limitá-las dinamicamente.
    • Como fazer
      Em parte já estará feito. Outras poderão estar pensadas ou em desenvolvimento.  A utilização de CPU está descrita acima...
    • Benefício
      Aumentar a capacidade do DBA controlar o sistema de forma a providenciar a melhor performance e melhor disponibilidade
       
     
Portanto, algumas destas foram adicionadas por mim no site de pedidos de melhoria (RFE). Se gostar delas, vote!. Comente conforme entenda naturalmente. E se tiver mais, por favor reporte-as na ferramenta.
Esta é a altura para a comunidade se unir e motivar e efectuar lobby em prol das funcionalidades que necessita. Não se esqueça que uma explicação clara será fundamental para o sucesso do pedido. Não hesite em discutir estes temas com colegas, outros utilizadores em fóruns ou reuniões de grupos de utilizadores etc.