(ヽ´ω`) < 助けてほしいマン

わからないことを助けてほしいマンが書くブログ

(ヽ´ω`) < OpenLDAP + SSSDでLinuxログインアカウント一元管理がさっぱりわからん - 2. ユーザエントリとか作成 -

(ヽ´ω`) < ユーザとグループとsudoers設定

前回のエントリでOpenLDAPの初期設定が完了し、 dc=tsugihagi,dc=local に対してデータを挿入できるようになったので、実際にログイン可能なユーザと、ユーザをまとめるグループ、さらにsudoersに該当するエントリを作成していく。

どのようにOUを設計していくかは、組織構成によって色んなパターンがあると思うので、ここではすごく簡単に下記のように作成していく。

  • ユーザはou=Users,dc=tsugihagi,dc=local 配下にフラットに作成していく。 (例 cn=sre-user1,ou=Users,dc=tsugihagi,dc=local
  • グループは ou=Groups,dc=tsugihagi,dc=local 配下にフラットに作成していく。 (例 `cn=SRE,ou=Groups,dc=tsugihagi,dc=local' , 'cn=Dev,ou=Groups,dc=tsugihagi,dc=local )
  • sudoersは ou=Sudoers,dc=tsugihagi,dc=local 配下に〜

(ヽ´ω`) < 想定するサーバ構成とユーザ情報、アクセス権

ここでは下記の条件を想定してユーザ情報を作成していく。

  • サーバは3台 Produciton, Staging, Development
  • グループは3つ managers, sre, dev
  • ユーザは4人 sre-manager, sre-senior-member, sre-member, dev-member
  • それぞれのユーザのアクセス権は下記の通り
    • ◎ ログイン可 sudo可
    • ○ ログイン可 sudo不可
    • ☓ ログイン不可
ユーザ グループ Production Staging Development
sre-manager managers, sre
sre-senior-member sre
sre-member sre
dev-member dev

上記の表より、下記の通りのポリシーを設定する。

  • managersグループは全てのサーバに対してログイン/sudo可
  • sreグループは全てのサーバに対してログイン可、加えてDevelopmentサーバにsudo可
  • devグループはStagingサーバに対してログインのみ可、Developmentサーバに対してログイン/sudo可
  • sre-senior-memberはsreグループの権限に加え、ユーザ個人に対してStagingサーバに対してsudo可を追加

(ヽ´ω`) < ベースドメインとOU

まずはベースDNの設定とOUの作成。 ベースDNは前回設定した dc=tsugihagi,dc=local を使用する。(base.ldif)

dn: dc=tsugihagi,dc=local
objectClass: dcObject
objectClass: organization
dc: tsugihagi
o: tsugihagi

dn: ou=Users,dc=tsugihagi,dc=local
objectClass: organizationalUnit
ou: Users

dn: ou=Groups,dc=tsugihagi,dc=local
objectClass: organizationalUnit
ou: Groups

dn: ou=Sudoers,dc=tsugihagi,dc=local
objectClass: organizationalUnit
ou: Sudoers

(ヽ´ω`) < ユーザ

自分が一番最初にOpenLDAPでユーザの一元管理を試してみたとき(もう10年近く前かな…)に感じた疑問が 「(ヽ´ω`) < そもそもLDAPのエントリは任意の場所(DITツリーでの位置)に任意の形式で作成できるんだけど、その中で何をもってユーザ情報と見做すの?」 だった記憶。

そんな昔の自分への回答。 ユーザは posixAccount , inetOrgPerson , ldapPublicKey の3つのオブジェクトクラスをもつエントリとなる。場所についてはどこでもOK。(今回の場合は認証情報を見に来るサーバ側で検索する位置・範囲を設定するため) それぞれに必要な属性を、各オブジェクトクラスのスキーマ定義を見て確認していく。

posixAccount のスキーマ定義は下記の通り。

objectclass ( 1.3.6.1.1.1.2.0 NAME 'posixAccount'
        DESC 'Abstraction of an account with POSIX attributes'
        SUP top AUXILIARY
        MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory )
        MAY ( userPassword $ loginShell $ gecos $ description ) )

MUSTなのが cn, uid, uidNumber, gidNumber, homeDirectory なのでこれは必須。 SSSDでは loginShell もうまいこと扱ってくれるので、こちらも入力しておく。 userPassword は公開鍵認証を使用するので不要。 gecosdescrption は、まぁ入れておいてもいいかな。

続いて inetOrgPerson のスキーマ定義。

# inetOrgPerson
# The inetOrgPerson represents people who are associated with an
# organization in some way.  It is a structural class and is derived
# from the organizationalPerson which is defined in X.521 [X521].
objectclass     ( 2.16.840.1.113730.3.2.2
    NAME 'inetOrgPerson'
        DESC 'RFC2798: Internet Organizational Person'
    SUP organizationalPerson
    STRUCTURAL
        MAY (
                audio $ businessCategory $ carLicense $ departmentNumber $
                displayName $ employeeNumber $ employeeType $ givenName $
                homePhone $ homePostalAddress $ initials $ jpegPhoto $
                labeledURI $ mail $ manager $ mobile $ o $ pager $
                photo $ roomNumber $ secretary $ uid $ userCertificate $
                x500uniqueIdentifier $ preferredLanguage $
                userSMIMECertificate $ userPKCS12 )
        )

実は、見ての通り全て属性がMAYなので inetOrgPerson は必須ではない。が、ユーザ管理という面から便利な属性を持っているので、オブジェクトクラスとして追加する。 今回は例として mail を入力しておく。

また上位のオブジェクトクラスであるorganizationalPersonのMUST属性として sn が指定されているので、こちらについても必須となる。

ここまでは、いわゆる一般的なOpenLDAPでユーザ情報を保持する属性の定義。 今回はパスワード認証ではなく、公開鍵認証のための公開鍵をOpenLDAP側で保持する。そのための属性が sshPublicKey で、その値を保持するオブジェクトクラスが ldapPublicKey となる。

# octetString SYNTAX
attributetype ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey'
        DESC 'MANDATORY: OpenSSH Public key'
        EQUALITY octetStringMatch
        SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )

# printableString SYNTAX yes|no
objectclass ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' SUP top AUXILIARY
        DESC 'MANDATORY: OpenSSH LPK objectclass'
        MUST ( sshPublicKey $ uid )
        )

必要な属性が設定され、作成したユーザ追加用LDIFはこんな感じ。(users.ldif)

dn: cn=sre-manager,ou=Users,dc=tsugihagi,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: ldapPublicKey
uid: sre-manager
uidNumber: 10001
gidNumber: 10000
homeDirectory: /home/user1
loginShell: /bin/bash
mail: sre-manager@tsugihagi.local
sn: sre
gecos: sre manager
sshPublicKey: ssh-rsa 11111
sshPublicKey: ssh-rsa 22222
sshPublicKey: ssh-rsa 33333

dn: cn=sre-senior-member,ou=Users,dc=tsugihagi,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: ldapPublicKey
uid: sre-senior-member
uidNumber: 10002
gidNumber: 10001
homeDirectory: /home/sre-senior-member
loginShell: /bin/bash
mail: sre-senior-member@tsugihagi.local
sn: sre
gecos: sre-senior-member
sshPublicKey: ssh-rsa 44444

dn: cn=sre-member,ou=Users,dc=tsugihagi,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: ldapPublicKey
uid: sre-member
uidNumber: 10003
gidNumber: 10001
homeDirectory: /home/sre-member
loginShell: /bin/bash
mail: sre-member@tsugihagi.local
sn: sre
gecos: sre-member
sshPublicKey: ssh-rsa 55555

dn: cn=dev-member,ou=Users,dc=tsugihagi,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: ldapPublicKey
uid: dev-member
uidNumber: 10004
gidNumber: 10002
homeDirectory: /home/dev-member
loginShell: /bin/bash
mail: dev-member@tsugihagi.local
sn: dev
gecos: dev-member
sshPublicKey: ssh-rsa 66666

(ヽ´ω`) < グループ

グループについては posixGroup オブジェクトクラスを持つエントリを作成する。

objectclass ( 1.3.6.1.1.1.2.2 NAME 'posixGroup'
        DESC 'Abstraction of a group of accounts'
        SUP top STRUCTURAL
        MUST ( cn $ gidNumber )
        MAY ( userPassword $ memberUid $ description ) )

cn , gidNumber は必須なので入力。 memberUid もMAYとなっているが所属しているユーザを指定するために必要なので、ユーザが存在しないグループを定義する場合以外は設定が必要。

ということで実際のエントリはこんな感じ。(groups.ldif)

dn: cn=sre,ou=Groups,dc=tsugihagi,dc=local
objectClass: posixGroup
gidNumber: 10001
cn: sre
memberUid: sre-manager
memberUid: sre-senior-member
memberUid: sre-member

dn: cn=dev,ou=Groups,dc=tsugihagi,dc=local
objectClass: posixGroup
cn: dev
gidNumber: 10002
memberUid: dev-member

dn: cn=managers,ou=Groups,dc=tsugihagi,dc=local
objectClass: posixGroup
cn: managers
gidNumber: 10003
memberUid: sre-manager

実はLDAPでグループを扱う際には"RFC2307 or RFC2307bis"というテーマについて考えないといけないのだが、これは独立したエントリで扱わないと長くなりそうなので… とりあえずここでは 「グループはposixGroupオブジェクトクラスを持ったエントリ内に、ユーザ名(memberUid)が列挙されて定義される」 と覚えておく。

(ヽ´ω`) < sudoers

sudoersはダウンロードした sudo.schema に定義されている sudoRole オブジェクトクラスを持つエントリを作成。

objectclass ( 1.3.6.1.4.1.15953.9.2.1 NAME 'sudoRole' SUP top STRUCTURAL
    DESC 'Sudoer Entries'
    MUST ( cn )
    MAY ( sudoUser $ sudoHost $ sudoCommand $ sudoRunAs $ sudoRunAsUser $ sudoRunAsGroup $ sudoOption $ sudoOrder $ sudoNotBefore $ sudoNotAfter $
        description )
    )

cn のみがMUSTとなっているが、sudoersファイルに記載されるような、こんな内容

## Allows people in group wheel to run all commands
%wheel  ALL=(ALL)       NOPASSWD: ALL

を実現するには sudoHost , sudoCommand , sudoOption のような他の属性を入力していく必要がある。

linuxjm.osdn.jp

ここで NOPASSWD: ALL を実現するためには

sudoOption: NOPASSWD

と書けば良さそうに思えるが、これはNG。正しくは

sudoOption: !authenticate

と書く必要がある。 困ったことに、この設定は sudoers.ldap のmanにも記載がなかったので、どうしたものか戸惑ったのだが、下記のページがすごく参考になった。

pig.made-it.com

このページに下記のような記載があったので、

The sudoers package contains a perl-script called sudoers2ldif, this script is provided in the /usr/share/doc/sudoers/ directory.

早速使ってみようと思って調べてみたが見当たらない。

sudo パッケージの内容を見てみると、 cvtsudoers と何やらそれらしい名前が。

# rpm -ql sudo
<-- snip -->
/usr/bin/cvtsudoers
<-- snip -->
/usr/share/man/man1/cvtsudoers.1.gz
<-- snip -->

man cvtsudoers してみるとDescriptionには

cvtsudoers can be used to convert between sudoers security policy file formats. The default input format is sudoers. The default output format is LDIF. It is only possible to convert a sudoers file that is syntactically correct.

とのことなので、早速試してみる。 -b オプションでベースとなるDNを指定してやる。ここでは ou=Sudoers,dc=tsugihagi,dc=local の配下にエントリを作成していくので、こんな感じ。

# cvtsudoers -b "ou=Sudoers,dc=tsugihagi,dc=local" /etc/sudoers
dn: cn=defaults,ou=Sudoers,dc=tsugihagi,dc=local
objectClass: top
objectClass: sudoRole
cn: defaults
description: Default sudoOption's go here
sudoOption: !visiblepw
sudoOption: always_set_home
sudoOption: match_group_by_gid
sudoOption: always_query_group_plugin
sudoOption: env_reset
sudoOption: env_keep=COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS
sudoOption: env_keep+=MAIL PS1 PS2 QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE
sudoOption: env_keep+=LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES
sudoOption: env_keep+=LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE
sudoOption: env_keep+=LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY
sudoOption: secure_path=/sbin:/bin:/usr/sbin:/usr/bin

dn: cn=root,ou=Sudoers,dc=tsugihagi,dc=local
objectClass: top
objectClass: sudoRole
cn: root
sudoUser: root
sudoHost: ALL
sudoRunAsUser: ALL
sudoCommand: ALL
sudoOrder: 1

dn: cn=%wheel,ou=Sudoers,dc=tsugihagi,dc=local
objectClass: top
objectClass: sudoRole
cn: %wheel
sudoUser: %wheel
sudoHost: ALL
sudoRunAsUser: ALL
sudoCommand: ALL
sudoOrder: 2

dn: cn=ec2-user,ou=Sudoers,dc=tsugihagi,dc=local
objectClass: top
objectClass: sudoRole
cn: ec2-user
sudoUser: ec2-user
sudoHost: ALL
sudoRunAsUser: ALL
sudoOption: !authenticate
sudoCommand: ALL
sudoOrder: 3

3つ目のエントリを見て分かる通り、sudoersファイルでの指定と同様に % をプレフィックスとして付与することで、グループに対してsudo権を設定することができる。 ここで指定するグループは /etc/group で管理されるグループは当然として、前項で定義したOpenLDAP上のグループに対しても適用が可能。

cvtsudoers の出力は上記のとおりだが、ローカルで定義されているユーザ・グループ対するsudoersはローカルのファイルで定義すべきで、ここでは実際に追加するエントリは下記の通りとなる。

dn: cn=production-server,ou=Sudoers,dc=tsugihagi,dc=local
objectClass: sudoRole
cn: production-server
sudoHost: ALL
sudoCommand: ALL
sudoUser: %managers

dn: cn=staging-server,ou=Sudoers,dc=tsugihagi,dc=local
objectClass: sudoRole
cn: staging-server
sudoHost: ALL
sudoCommand: ALL
sudoUser: %managers
sudoUser: sre-senior-member

dn: cn=dev-server,ou=Sudoers,dc=tsugihagi,dc=local
objectClass: sudoRole
cn: dev-server
sudoHost: ALL
sudoCommand: ALL
sudoUser: %managers
sudoUser: %sre
sudoUser: dev-member

cnの値がサーバ名になっていることがわかると思う。
権限をどの単位で管理するかは既存のポリシーや組織によってまちまちだと思うが、ここではファイルによるsudoers管理と同様に、サーバからみてどのユーザ・グループに権限を付与するか、という視点で設定する。

(ヽ´ω`) < LDIF流し込み

作成された base.ldif , users.ldif , groups.ldif , sudoers.ldif をOpenLDAPに流し込んでいく。

ここで注意、改めて olcDatabase={2}hdb,cn=config の設定を見てもわかるとおり、 olcAccess 属性が設定されていない。

[root@ip-172-16-1-209 ~]# ldapsearch -Y EXTERNAL -H ldapi:/// -b "olcDatabase={2}hdb,cn=config"
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
# extended LDIF
#
# LDAPv3
# base <olcDatabase={2}hdb,cn=config> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# {2}hdb, config
dn: olcDatabase={2}hdb,cn=config
objectClass: olcDatabaseConfig
objectClass: olcHdbConfig
olcDatabase: {2}hdb
olcDbDirectory: /var/lib/ldap
olcDbIndex: objectClass eq,pres
olcDbIndex: ou,cn,mail,surname,givenname eq,pres,sub
olcSuffix: dc=tsugihagi,dc=local
olcRootDN: cn=Manager,dc=tsugihagi,dc=local
olcRootPW: {SSHA}SFlHUkAMFkH6xUPA61IEqDnTY2lN2YwF

この状況では、全てのユーザはread権限を持つが書き込み権限を持つのはRootDNのみとなる。

そのため書き込みのために発行する ldapadd に渡すオプションは下記のとおりとなる。

# ldapadd -D "cn=Manager,dc=tsugihagi,dc=local" -whogehoge -H ldap://127.0.0.1 -f base.ldif
# ldapadd -D "cn=Manager,dc=tsugihagi,dc=local" -whogehoge -H ldap://127.0.0.1 -f users.ldif
# ldapadd -D "cn=Manager,dc=tsugihagi,dc=local" -whogehoge -H ldap://127.0.0.1 -f groups.ldif
# ldapadd -D "cn=Manager,dc=tsugihagi,dc=local" -whogehoge -H ldap://127.0.0.1 -f sudoers.ldif

実際にエントリが書き込まれたかを確認。

# ldapsearch -Y EXTERNAL -H ldapi:/// -b "dc=tsugihagi,dc=local"

ここまででOpenLDAP側の設定は完了。

続いて実際に認証が行われる側、SSHDが稼働しているサーバ側のSSSD設定を行っていく。