Primeiros Passos com o LDAP

por Luke A. Kaines - 11/08/2001
Artigo publicado originalmente em http://www.oreillynet.com/pub/a/linux/2001/11/08/ldap.html, na The O'Reilly Network.

Escrever este artigo acabou sendo mais difícil que eu esperava. Inicialmente eu comecei com uma explicação profunda do LDAP como protocolo, mas percebi que o objetivo real aqui era poder trabalhar com o LDAP agora mesmo, não após ler 50 páginas de explicações abstratas.

Com este objetivo em mente, começaremos a trabalhar com o LDAP e um ambiente de trabalho semi-real. Especificamente, iremos configurar um diretório LDAP básico para armazenar as contas de usuário Unix, além de um script para passar estas contas para um sistema Unix -- esta é uma das coisas para qual você deve usar o LDAP. Este exemplo também será útil para demonstrar que mesmo que sua versão de Unix não possa realizar autenticação de usuários diretamente a partir do LDAP, você ainda pode armazenar informações de seus usuários no LDAP e obter todos os objetivos que provêm disto.

O objetivo

Conforme mencionado em meu artigo anterior, o LDAP foi desenvolvido como um método para consolidar as informações de acesso, autenticação e autorização (AAA, ou Triplo-A). Por si mesmo, ele é útil, por que você vai estar mantendo todas as informações em um único lugar, ao invés de vários locais. Entretanto, você poderia conseguir isto usando qualquer banco de dados antigo. O que torna o LDAP especialmente apropriado para armazenar as informações AAA é que todas as operações LDAP tem lugar no contexto da informação AAA, ao invés de forçar a aplicação de fornecer ou interpretar o contexto. As operações falham ou terminam com sucesso sem necessidade da aplicação entender as regras envolvidas.

Se você tentou colocar toda a mesma informação AAA em um banco de dados (o que pode ser algo difícil, por que você tem que definir todos os padrões para armazenamento de informações, algo que o LDAP já tem pronto para você usar), então cada uma de suas aplicações devem analisar aquelas informações e levar as mesmas em conta para cada operação AAA. Se você usar o LDAP, entretanto, já existem métodos para armazenar as informações AAA, apesar de ainda não estarem definidas em uma RFC, e o servidor LDAP aplicar as regras para operações AAA ao invés das aplicações. Isto não só torna a vida dos desenvolvedores de aplicações mais fácil, mas também elimina a chance de aplicações ou usuários mal comportados ignorar as regras de AAA e fazer acesso direto ou modificação do conteúdo de diretórios, exceto, obviamente, atravéz de um compromisso tradicional da segurança.

Assim, nosso objetivo aqui é criar uma infraestrutura AAA, com o LDAP no seu núcleo. A maioria de nossas informações de autenticação já existem, na forma de contas de usuários, e para o nosso artigo, vamos assumir que estas contas de usuários estão em máquinas Unix. Nosso objetivo, então, é colocar estas informações de usuários no LDAP, administrar as mesmas inteiramente no LDAP, e usar a mesma como a origem de todas as operações de AAA em outras aplicações. Uma vez que tenhamos a informação de autenticação configurada, devemos acrescentar as informações de acesso e autorização.

Este artigo vai tratar da primeira tarefa, substituindo os métodos padrão de manter contas Unix com métodos que usem o LDAP. Mais tarde, provavelmente iremos dar exemplos de manutenção baseada na Web de nossos dados LDAP e algumas aplicações que podem obter vantagens destes novos dados centralizados.

As ferramentas

Conforme mencionado no artigo anterior, quase toda linguagem moderna tem uma API LDAP; assim, há uma disponibilidade quase infinita de ferramentas. Por sorte, devido a simplicidade do protocolo, a maioria das APIs funciona de forma semelhante. Para iniciar imediantamente, iremos começar com as ferramentas de linha de comando (das quais existem múltiplas versões), por que são de uso direto e presentes em praticamente todos os sistemas.

Há somente três tipos básicos de operações LDAP, e cada tipo básico tem poucos subtipos: interrogação (pesquisa, comparação), atualização (acrescentar, modificar, renomear, excluir), e ligação/controle (ligar, desligar, abandonar). Note que não há uma operação de "leitura"; se você quer ler uma entrada, use uma operação de leitura para recuperar a mesma.

Para mover nossas contas Unix para o LDAP, precisamos converter as mesmas em uma forma que o servidor LDAP possa entender. Devido à simplicidade da conversão de um arquivo passwd para o LDIF (veja abaixo), iremos deixar a conversão como um exercício para o leitor. Ao invés disto, iremos começar com uma conta de usuário já convertida e vamos acrescentar a mesma ao LDAP:

$ ldapadd -D "cn=Directory Manager" -h server
password: ********
dn: uid=luke,ou=People,dc=domain,dc=com
objectclass: top
objectclass: posixAccount
uid: luke
cn: Luke A. Kanies
cn: Luke Kanies
cn: Kanies, Luke
uidNumber: 100
gidNumber: 14
homeDirectory: /home/luke
userPassword: {crypt}8SYYCOBH.aIII
gecos: Luke A. Kanies
^D
adding new entry uid=luke,ou=People,dc=domain,dc=com
$

Esta operação usa duas da três operações básicas do LDAP, uma operação de atualização e uma operação de ligação, junto com uma operação implícita de desligamento. Devemos fazer a ligação como usuário privilegiado para poder modificar o diretório (este exemplo utiliza um usuário especial que todos os servidores iPlanet Directory Server possuem).

Fornecemos então os dados para a entrada que pretendemos acrescentar, que está no formato LDAP Date Interchange Format (LDIF), o formato texto padrão para dados LDAP. Note que cada linha possue um nome de atributo, seguido de dois pontos e espaço, e o valor do atributo. Certamente faze sentido uma operação de pesquisa do LDAP retornar este formato, de forma que pode ser imediatamente informada a um servidor LDAP, mas algumas ferramentas -- incluindo as que são distribuídas com o Solaris -- não imprimem no formato padrão LDIF, requerendo estupidamente que façamos uma conversão.

Componentes de um registro LDAP

Agora que veremos como um registro LDAP se parece, vamos ver todos seus pedaços para entender o mesmo.

Distinguished Names

A primeira linha é o que chamamos de "Distinguished Name" ("Nome Distinto"), ou dn. Como ele é usado para dizermos para o servidor qual objeto estamos trabalhando, a linha dn sempre deve ser definida primeiro.

O dn é como um registro é referenciado univocamente em um servidor LDAP, da mesma forma que um path absoluto ou um nome de domínio completamente qualificado. Note que o dn é representado de forma semelhante aos nomes DNS, com a informação mais específica primeira, em oposição a nomes de path, que tem a informação menos específica primeiro. Ao contrário do que se parece, a origem desta árvore LDAP, também chamada de "naming context" ("contexto de nomes") é dc=domain, dc=com, e não dc=com.

Não existem exigências sobre qual o nome da origem de sua árvore LDAP deve ser, mas existem dois padrões: ou o padrão que eu utilizo aqui, que divide um domínio em vários componentes de domínio, ou um em que a organização é referida no nível mais alto (por exemplo, o=domain.com). Qual você deve seguir será respondido de forma diferente para cada um que você consultar, e por que você deve seguir este ou aquele padrão também será respondido de forma diferente. Eu escolhi o seguinte padrão de componente de domínio por que parece ser mais popular atualmente -- a Sun e a Microsoft estão recomendando/exigindo ele. Uso o que parece mais fácil de usar e que seja mais apropriado para a forma que você está planejando usar os dados.

O resto do dn consiste de uma ramificação na árvore, ou=People, e um par atributo-valor que identifica univocamente este registro, uid=luke. Este par atributo-valor, quando separado do dn, é chamado de "Relative Distinguished Name" ("Nome Distinto Relativo"), ou rdn, e identifica univocamente este objeto neste nível da árvore.

Como os nomes de path e nomes DNS semper se referem ao nome do objeto em questão, tudo que se precisa especifica é este valor, mas o LDAP pode usar qualquer atributo para criar um rdn, e em resultado disto tanto o atributo quanto seu valor deve ser especificado. O dn para esta entrada poderia ser cn=Luke A. Kanies,ou=People,dc=domain,dc=com, já que não há ninguém mais na ou=People com "Luke A. Kanies" como valor de cn.

Escolhemos uid aqui, por que sempre iremos garantir que este é único apra cada entrada -- qualquer outra coisa não irá funcionar corretamente em nossos sistemas Unix, e o Directory Server iPlanet pode ser convenientemente configurado para não permitir valores uid duplicados (ou qualquer outro atributo). Aparentemente o Active Directory da Microsoft exige que o cn seja usado para criar o rdn, o que é inconveniente por que o cn é quase que garantidamente não único, e este é o nome completo do usuário. Muito obrigado!

Object classes

A seguir em nossa entrata existem dois atributos objectclass. Estes atributos definem qual o tipo de objeto que a entrada é. Entretanto, o conceito de objeto no LDAP é extremamente simples: ele apenas define quais atributos um registro deve ter e quais atributos um registro pode ter. Todas as classes de objeto herdam as especificações de suas classes de objeto pai e acrescentam suas próprias. O atributo objectclass não é um atributo especial, entretanto -- em todas operações LDAP ele é tratado exatamente como outros atributos LDAP, mas a modificação de classes de objeto determina se o objeto será aceitável ao servidor após a operação.

A definição acima de objeto LDAP é importante, por que é difícil convencer-se como esta definição é simples. Novamente, um objeto somente define quais atributos devem ou podem ser armazenados com uma entrada. Eu posso criar um ovjeto que tenha tanto a classe de objeto posixAccount e um objeto de classe printer; esta combinação pode ser bastante contraditória para você e eu, mas para o LDAP, dados são apenas dados, e não possuem nenhum significado intrínseco. Uma das coisas que faz o LDAP legal é que os atributos significam algo para os humanos, mas os humanos que trabalham com os dados devem reter este significado nomeandos classes de objetos e atributos de forma inteligente e criando objetos que realmente façam sentido. Isto é mais difícil que parece, e decidir como tudo isto será feito é o primeiro passo para usar o LDAP. Felizmente, a maior parte das classes de objetos que você precisará fazem parte da especificação LDAP (apesar de posixAccount ser parte de uma RFC posterior), então, pelo menos inicialmente, você não terá de se preocupar muito com isto.

Note que você não pode simplesmente criar classes de objetos e acrescentar as mesmas a um objeto, o servidor deve ter cada classe de objeto definida, no que é chamado de schema (esquema). Se você tentar acrescentar um registro com uma classe de objeto indefinida, você receberá uma mensagem de violação do esquema, e a operação irá falhar. Como você devine uma classe de objeto para o servidor vaira de servidor para servidor, mas a maioria deles tem isto bem documentado.

Neste caso, declaramos que nosso registro será dos tipos top e posixAccount. A classe de objeto top somente exige que o atributo objectclass esteja presente, e por definição é a classe objeto pai de todas as outras classes de objeto LDAP. Dado este fato, e o fato que os objetos sempre listam as classes de objeto, elas são uma instância junto com todas as outras classes de objeto pai, todo objeto em um banco de dados LDAP irá listar top como uma classe de objeto. A classe de objeto posixAccount é definida em uma RFC por Luke Howard, que realizou um trabalho significante para permitir que o LDAP armazene contas Unix, e forneça meios de armazenar todas as informaçãoes de um arquivo passwd no LDAP.

Atributos

Como você já deve ter adivinhado, os atributos podem ter múltiplos valores. Se um atributo suporta múltiplos valoers está declarado na definição do atributo, mas a maioria suporta.

Conforme mencionado anteriormente, a classe de objeto top exige que a objectclass esteja presente, e só. Assim, a presença do top não tem outro efeito neste registro. A classe de objeto posixAccount exige cn, uid, uidNumber, gidNumber, e homeDirectory, e permite os campos userPassword, loginShell, gecos, e description. Como um login válido deve ter toda esta informação, exceto a descrição, incluímos toda ela em nosso registro.

Tdos os nossos valores de atributos são auto-explicativos, exceto o valor de userPassword. Como a maioria (todas?) as máquinas Unix armazenam as senhas no formato crypt, e por que iremos fazer um dump do registro LDAP em um arquivo passwd, também devemos armazenar a senha LDAP no formato crypt. Muitos servidores LDAP suportam múltiplos formatos de senha, e incluímos um exemplo de como especificar o formato. Os servidores que suprotam múltiplos formatos devem permitir a especificação do formato default, e é recomendado que você configure o formato default para crypt se você pretende usar seu servidor LDAP para armazenar contas Unix. A única exceção para isto é se todas as suas máquinas Unix possam fazer a autenticação diretamente do servidor LDAP, e podem entender outros formatos que não o crypt.

Usando os dados

Ok, agora temos o registro no servidor LDAP, e temos um entendimento básico do registro em si. Agora é hora de começar a usar os dados. Como mencionado antes, se você quer ler um registro, tem que fazer uma pesquisa por ele. Então, para verificar se nosso registro está presente, é o que faremos.

Existe um total de oito (sim, oito) diferentes opções para cada pesquisa LDAP, mas a maioria delas tem valores padrão razoáveis e geralmente fazem sentido. Não usaremos muitas destas opções de início, para manter a simplicidade.

Um exemplo de pesquisa:

$ ldapsearch -h server -b "dc=domain,dc=com" "(uid=luke)"
uid=luke,ou=People,dc=madstop,dc=com
objectclass=top
objectclass=posixAccount
uid=luke
cn=Luke A. Kanies
uidnumber=100
gidnumber=14
homedirectory=/home/luke
gecos=Luke A. Kanies
$

Este resultado é em um sistema Solaris; horror dos horrorres -- note que a saída não está no formato LDIF (sinais de igual são usados ao invés de dois-pontos e espaços). Incrível mas verdadeiro. Note que a senha não foi apresentada; isto acontece por razões de segurança, da mesma forma que o /etc/shadow somente pode ser lido por usuários privilegiados.

Em nossa pesquisa, incluímos duas flags e um argumento. A primeira flag é óbvia: especificamos o servidor. A segunda flag especificou a base da pesquisa, de forma similar a como especificar o ponto inicial de uma pesquisa usando o comando find; poderíamos ter especificado qualquer ramo incluindo o próprio objeto. O argumento da pesquisa é chamado de filtro, e é como especificamos o registro ou registros específicos que queremos. Todos os registros que combinarem com o filtro serão retornados. Felizmente o formato do filtro é um pouco sofisticado, de forma que você não terá dificuldades em fazer pesquisas complexas para encontrar exatamente o que está procurando, mas estou somente usando os filgros básicos para conseguir o que quero. Para mais informações, consulte uma referência.

Como conhecemos o dn do registro, poderíamos fazer uma pesquisa deste jeito:

$ ldapsearch -s base -h server -b "uid=luke,ou=People,dc=domain,dc=com" "(objectclass=*)"

Como conhecemos o dn, pdoeriamos especificar aquele dn como base da pesquisa. Se fizermos isto, entretanto, temos que mudar o escopo da pesquisa. Por padrão, uma consulta LDAP procura por objetos em qualquer ponto da hierarquia começando na base da pesquisa. Você pode especifica um escopo de "um", que somente pesquisa o próximo nível abaixo na hierarquia, e base, que somente pesquisa a base. Eu percebi algumas discrepâncias quando usando o iPlanet Dierctory Server 4.x, geralmente a base da pesquisa é retornada se ela combina com o filtro, mas já vi alguns exemplos em que não é este o caso. O método acima é o método preferido de recuperação se você conhece a dn da entrada, por que é mais rápida e exige menos trabalho do servidor.

Note também que nosso filtro mudou. Anteriormente procuramos por um valor específico de uid, mas como esmos usando o dn do registro como base de nossa pesquisa, para recuperar o mesmo diretamente usamos o que é chamado de filtro de existência. Quando é feita uma pesquisa por um atributo com o valor *, o servidor LDAP retorna todos registros que possuem algum valor para aquele atributo. Como todos os registros possuem valor para objectclass, usar um filtro objectclass=* é o método padrão para retornar todos os registros de uma dada base e escopo. A razão pela preferência de um filtro de existência sobre um filtro mais específico é que um filtro específico obriga o servidor LDAP trabalhar mais, e o uso de um filtro de existência aqui dá o mesmo resultado com menos trabalho para o servidor.

Criando a conta Unix

Está tudo bem, temos os dados no servidor LDAP, e podemos examinar o mesmo, mas como iremos transformar o mesmo em uma conta Unix?

Assumindo que você tenha acrescentado este registro e todas as outras entradas desejadas no seu servidor LDAP, tudo que é necessário é apenas um script shell simples para converter estes dados para os arquivos passwd e shadow:

#!/usr/bin/bash
#
# shell script to convert from LDAP to passwd/shadow files
IFS='
' # set the internal file separator to a carriage return, in case
  # attributes have spaces or tabs in them

# look for all entries which have the posixAccount objectclass
# we have to authenticate as a privileged user in order to read the
# password
for entry in $(ldapsearch -h server -D \
 "uid=sysadmin,ou=People,dc=domain,dc=com" -w password \
 -b dc=domain,dc=com "(objectclass=posixAccount)"); do

  # this is the logic that prints out the passwd entry for the
  # previous entry every time we hit a DN line
  echo $line | grep "dc=madstop,dc=com" > /dev/null
  if [ $? == 0 -a ! -z "$uid" ]; then
    echo $uid:+:$uidnumber:$gidnumber:$gecos:$homedirectory:$usershell \
    >> newpasswd
    echo $userpassword >> newshadow
  fi

  # this uses some trickery to cause the shell to interpret
  # ldapsearch output as variable assignments; this may need
  # to be modified depending on the output of the ldapsearch command,
  # but this works with the ldapsearch supplied on Solaris 8
  newline=$(echo $line | sed "s/'/'\''/g
    s/=/='/
    s/$/'/")
  eval $newline
done
# this echo line puts our last entry into the files, because our
# method of dumping the entries is triggered by the next entry,
# and there is no next entry for the last entry
echo $uid:+:$uidnumber:$gidnumber:$gecos:$homedirectory:$usershell \
>> newpasswd
echo $userpassword >> newshadow

# backup the passwd and shadow files, and put the new ones in place
for file in passwd shadow; do
  cp $file $file.bak
  cp new$file $file
done

Não é bonito? Não mesmo. Eu deveria ter feito isto em Perl? Sim, mas eu não quero entrar nas API(s) do Perl. Em vez disto, este script irá pegar seus registros LDAP e criar arquivos passwd e shadow válidos para o Solaris. Eles podem requerer modificações para outros Unices. Ele não é terrivelmente interessante ou útil se você tem somente um par de máquinas Unix, mas se você tem 200 máquinas todas rodando diferentes versões de Unix, você pdoe facilmente fazer uma versão deste script para cada versão do Unix (se necessário), e assim consolidar todas as informações de contas em um repositório de dados.

Uma coisa que ignoramos é a informação de administração de senha. Toda a informação relacionada a validade e administração de senhas é armazenada na classe de objeto shadowAccount. É direto para usar, e só teria complicado este artigo, por isto é deixado como exercício para o leitor. Outra coisa que ignoramos é a segurança, ou seja, estamos fazendo uma autenticação em texto plano com o servidor LDAP e toda a comunicação é em texto plano. Um próximo artigo começará explicando como incorporar criptografia ao seu serviço de diretório. A autenticação SSL não é muito mais complicada que a autenticação simples.

Conclusão

Provavelmente agora conseguimos acrescentar as informações das contas Unix ao LDAP, e fornecemos uma forma de passar a informação de volta e repopular os arquivos passwd e shadow. Sim, ignoramos os arquivos de grupos, que também são importamos, e assumimos que todas as contas estarão presentes em todas as máquinas. Felizmente não é um salto muito grande colocar as informações dos grupos no LDAP, e é relativamente fácil somente colocar certas classes de usuários em várias classes de máquinas, por exemplo, somente acrescentando contas de desenvolvedores e administradores de sistema nos servidores web, e somente administradores de sistemas e DBA nos servidores de banco de dados. De fato, conforme você começa a usar o LDAP, voc~e vai descobrir que, como em muitos outros processos de automação/centralização, a parte mais difícil é decidir como fazer as classificações acima. A maior parte das companhias confia no administrador do sistema para decidir se um usuário terá uma conta em algum lugar, e a freqüência de troca de senhas, e mesmo se existem regras claras, estas regras são geralmente obedecidas somente por administradores de sistemas, e não pelas ferramentas que eles utilizam.

Para aproveitar todas as vantagens do LDAP, todas estas regras devem ser colocadas no LDAP, de forma que todas estas decisões possam ser feitas por ferramentas automatizadas ao invés de pessoas. É um investimento feito apenas uma vez em direção a menos trabalho. Felizmente, o desenvolvimento e codificação destas regras é muito bom para seus negócios mesmo se você não planeja usar o LDAP, e você provavelmente irá descobrir que é um processo benéfico por si mesmo.

Conforme progredimos nesta série de artigos, iremos desenvolver uma arquitetura LDAP para uma organização simples. Um dos aspectos deste processo será a criação destas regras LDAP que irão determinar quem tem acesso a quais serviços e informações, incluindo quem tem contas em quais máquinas.

O próximo passo

Introduzimos alguns aspectos de como a autenticação funciona dentro do LDAP e como você pode migrar seus dados de autenticação existentes para o LDAP. No próximo artigo desta série, iremos explorar a administração destes dados em uma interface web, juntamente com controle básico de AAA, que é necessário para ter uma administração aceitável. Se eu puder colocar isto junto a tempo, poderemos estar prontos para incorporar o SSL na mistura.

Luke A. Kanies é um administrador de sistemas Unix que está mais interessado no sistema operacional do que no que pode ser feito com o mesmo. Ele trabalha atualmente como contratante e pesquisador, tentando fazer um melhor sysadmin.


Original: oreillynet.com Copyright © 2000 O'Reilly & Associates, Inc.
Tradução: Copyright © 2001 Cesar A. K. Grossmann

1