#はじめに
前回投稿の続きで、今度はActive Directoryにエントリ(ユーザーアカウント)を追加してみたいと思います。
#前提条件
Active Directoryを外部からLDAP経由で操作する場合、ユーザーパスワード(unicodePwd)周りについて、以下のような制約があります。
- パスワードを設定/変更する場合、LDAP over SSL(LDAPS)で接続する必要があります。
- パスワードが未設定の場合、アカウントオプション(userAccountControl)を設定することは出来ません。
- ダブルクォーテーション付のパスフレーズを、UTF-16(リトルエンディアン、BOM無)に変換して設定する必要があります。
また、AD側のLDAPS設定方法ですが、今回は投稿内容の趣旨から若干逸れますので省略します。
#エントリ内のユーザーアカウント確認
サンプルコードを書く前に、dsquery コマンドを実行して既存のユーザー情報を覗いてみます。
>dsquery * "cn=user01,cn=Users,dc=mydomain,dc=local", -attr *
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: user
cn: user01
description: ユーザ01
distinguishedName: CN=user01,CN=Users,DC=mydomain,DC=local
instanceType: 4
whenCreated: 12/09/2005 08:15:38
whenChanged: 06/27/2016 10:19:58
displayName: ユーザ01
uSNCreated: 8298
memberOf: CN=Users,CN=Builtin,DC=mydomain,DC=local
uSNChanged: 8298
name: user01
objectGUID: {3844A2B1-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
userAccountControl: 66048
badPwdCount: 0
codePage: 0
countryCode: 0
badPasswordTime: 131368051478279796
lastLogon: 131402372151448047
pwdLastSet: 127785897385156250
primaryGroupID: 513
objectSid: S-1-5-21-2910603885-XXXXXXXXXX-XXXXXXXXXX-XXXX
accountExpires: 9223372036854775807
logonCount: 58
sAMAccountName: user01
sAMAccountType: 805306368
userPrincipalName: user01@mydomain.local
objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=mydomain,DC=local
dSCorePropagationData: 01/01/1601 00:00:00
ADsPath: LDAP://DC1.mydomain.local/CN=user01,CN=Users,DC=mydomain,DC=local
上記のように、指定したユーザー(DN: cn=user01,cn=Users,dc=mydomain,dc=local)の属性情報が、LDIFファイル形式で表示されました。
この属性の中から、最低限必要と思われる属性のみを指定して、新しくユーザーを登録してみたいと思います。
#サンプルコード
あくまでもサンプルコードですので、「動けば良い」レベルで書いています。
このコードでは、ドメインmydomain.local
にtestuser
というユーザーアカウントを作成しています。
package main
import (
"fmt"
"log"
"gopkg.in/ldap.v2"
"golang.org/x/text/encoding/unicode"
"crypto/tls"
)
const (
LDAPSV = "dc1.mydomain.local"
PROTO = "tcp"
PORTNO = 636
BINDDN = "cn=Administrator,cn=Users,dc=mydomain,dc=local"
BINDPW = "XXXXXXXX"
)
func main() {
/*
LDAPサーバへ接続 (LDAPS)
>InsecureSkipVerify=trueにより、SSLがオレオレ証明書でも接続可能
*/
tlsConfig := &tls.Config{InsecureSkipVerify: true}
l, err := ldap.DialTLS(PROTO, fmt.Sprintf("%s:%d", LDAPSV, PORTNO), tlsConfig)
if err != nil {
log.Fatal(err)
}
fmt.Println("Ldap server connected.")
defer func() {
l.Close()
fmt.Println("Ldap server disconnected.")
}()
//LDAPサーバ認証(バインド)
err = l.Bind(BINDDN, BINDPW)
if err != nil {
log.Fatal(err)
}
fmt.Println("Ldap server logged in.")
/*
ユーザーパスワード変換(ASCII => UTF16LE)
>パスワード(ダブルクォーテーション付)をUTF16リトルエンディアン(BOM無)に変換
*/
utf16 := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
u16pass, err := utf16.NewEncoder().String("\"p@ssw0rd\"")
if err != nil {
log.Fatal(err)
}
//追加リクエスト作成
addRequest := ldap.NewAddRequest("cn=testuser,cn=Users,dc=mydomain,dc=local")
addRequest.Attribute("cn", []string{"testuser"})
addRequest.Attribute("displayName", []string{"テストユーザ"})
addRequest.Attribute("objectCategory", []string{"CN=Person,CN=Schema,CN=Configuration,DC=mydomain,DC=local"})
addRequest.Attribute("objectClass", []string{"top", "person", "organizationalPerson", "user"})
addRequest.Attribute("sAMAccountName", []string{"testuser"})
addRequest.Attribute("userPrincipalName", []string{"testuser@mydomain.local"})
addRequest.Attribute("unicodePwd", []string{u16pass})
addRequest.Attribute("userAccountControl", []string{"66048"})
//追加リクエストを基にエントリ追加
err = l.Add(addRequest)
if err != nil {
log.Fatal(err)
}
fmt.Println("Ldap server entry added.")
}
多少汚いコード(!)ですが、何をやっているかはお解り頂けるかと思います。
#軽く解説
##LDAPサーバへの接続
前回の検索編はLDAPでの接続でしたが、今回は予めADサーバをLDAPSに対応するよう設定し、LDAP over SSL(port=636)で接続しています。
TLS設定(tlsConfig)でInsecureSkipVerify: true
とあるのは、サーバ証明書に「オレオレ証明書」を使っていることによる証明書エラーを回避する為です。
tlsConfig := &tls.Config{InsecureSkipVerify: true}
l, err := ldap.DialTLS(PROTO, fmt.Sprintf("%s:%d", LDAPSV, PORTNO), tlsConfig)
if err != nil {
log.Fatal(err)
}
fmt.Println("Ldap server connected.")
defer func() {
l.Close()
fmt.Println("Ldap server disconnected.")
}()
##ユーザーパスワード変換
この箇所で"p@ssw0rd"
というASCII文字列を、UTF-16LE(BOM無)に変換する処理を行っています。
余談ですが、WindowsでUnicode
というと、UTF-16LE
を指すそうな。
utf16 := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
u16pass, err := utf16.NewEncoder().String("\"p@ssw0rd\"")
if err != nil {
log.Fatal(err)
}
##追加リクエスト作成
まずはDN(cn=testuser,cn=Users,dc=mydomain,dc=local)を引数に、リクエストで用いるインスタンスを作成し、その後必要な属性値をひとつずつセットしています。
尚、アカウント制御の属性(userAccountControl)についてはこちら等を参考に、環境に合わせて変更する必要があります。
ちなみに今回指定した10進66048
ですが、16進で表すと0x10200
となります。
addRequest := ldap.NewAddRequest("cn=testuser,cn=Users,dc=mydomain,dc=local")
addRequest.Attribute("cn", []string{"testuser"})
addRequest.Attribute("displayName", []string{"テストユーザ"})
addRequest.Attribute("objectCategory", []string{"CN=Person,CN=Schema,CN=Configuration,DC=mydomain,DC=local"})
addRequest.Attribute("objectClass", []string{"top", "person", "organizationalPerson", "user"})
addRequest.Attribute("sAMAccountName", []string{"testuser"})
addRequest.Attribute("userPrincipalName", []string{"testuser@mydomain.local"})
addRequest.Attribute("unicodePwd", []string{u16pass})
addRequest.Attribute("userAccountControl", []string{"66048"})
サンプルコードでは、属性情報をハードコーディングしていますが、実際にはLDIF、CSV等のテキストファイルやRDBMSを読み込み、順次処理するようなロジックを書くことになると思います。
#実行してみる
サンプルコードを実行すると、エラーが無ければ以下のようなメッセージとともに正常終了します。
>go run ldapadd.go
Ldap server connected.
Ldap server logged in.
Ldap server entry added.
Ldap server disconnected.
#確認してみる
ADサーバ側で dsqueryコマンドを実行して、testuser が無事に登録されていることを確認します。
>dsquery * "cn=testuser,cn=Users,dc=mydomain,dc=local" -attr *
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: user
cn: testuser
distinguishedName: CN=testuser,CN=Users,DC=mydomain,DC=local
instanceType: 4
whenCreated: 06/23/2017 03:02:24
whenChanged: 06/23/2017 03:08:55
displayName: テストユーザ
uSNCreated: 24753
uSNChanged: 24757
name: testuser
objectGUID: {D7844598-XXXX-XXXX-XXXX-XXXXXXXXXXXX}
userAccountControl: 66048
badPwdCount: 0
codePage: 0
countryCode: 0
badPasswordTime: 0
lastLogoff: 0
lastLogon: 0
pwdLastSet: 131426605443450970
primaryGroupID: 513
objectSid: S-1-5-21-2873641411-XXXXXXXXXX-XXXXXXXXXX-XXXX
accountExpires: 9223372036854775807
logonCount: 0
sAMAccountName: testuser
sAMAccountType: 805306368
userPrincipalName: testuser@mydomain.local
objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=mydomain,DC=local
dSCorePropagationData: 01/01/1601 00:00:00
lastLogonTimestamp: 131426609359530970
ADsPath: LDAP://DC1.mydomain.local/CN=testuser,CN=Users,DC=mydomain,DC=local