はじめに
お疲れ様です。矢儀 @yuki_ink です。
なんかもう、Windows卒業したいな、、
けどドメインユーザもらってるしな、、
ドメインユーザのパスワード変更とか、Linuxから簡単にできないかな、、
Linuxから簡単に、ドメインユーザのパスワードを変更したい!!
ということで張り切っていきましょう。
やったこと
前提
1. リソースを作る
2. シェルスクリプト実行用サーバのセットアップ
- 2.1. openldap-clients インストール
- 2.2 Microsoft ADにLDAPS通信する
3. シェルスクリプトを作成する
1. リソースを作る
VPC・サブネットから、AWS Managed Microsoft AD・EC2まで、必要なリソースをすべて作成するCloudFormationテンプレートを用意しまた。
今回はこれを使ってリソースの作成を行います。
前回からの差分はLinux Serverが追加になっているところだけです。
前回記事を参考にCloudFormationスタックを作成された場合、変更セットで差分を反映してください。
CloudFormationテンプレート
事前にキーペアを作成していることを前提にしています。
パラメータ KeyName
でキーペア名を指定してください。
Windows ServerのAMIは、今回は「Windows_Server-2019-English-Full-Base-2024.11.13」を利用しました。
東京リージョンでは ami-032985d75659a9d00
です。
Linux ServerのAMIは、「RHEL-8.4.0_HVM-20230419-x86_64-41-Hourly2-GP2」を利用しました。
東京リージョンでは ami-0d99111f48bcb5564
です。
今回は検証のため、EC2はパブリックサブネットに配置し、インターネット経由で接続します。
パラメータ AllowedPublicIP
で指定したIPレンジからのRDP/SSHを許可するようにしていますが、扱いにはご注意ください。
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation template to create VPC, subnets with AZ selection, AWS Managed MSAD, and EC2 instances using Key Pair authentication.
Parameters:
VpcCidr:
Type: String
Default: 10.0.0.0/16
Description: CIDR block for the VPC
Subnet1Cidr:
Type: String
Default: 10.0.1.0/24
Description: CIDR block for AWS Managed MSAD subnet 1
Subnet1Az:
Type: String
Default: us-east-1a
Description: Availability Zone for AWS Managed MSAD subnet 1
Subnet2Cidr:
Type: String
Default: 10.0.2.0/24
Description: CIDR block for AWS Managed MSAD subnet 2
Subnet2Az:
Type: String
Default: us-east-1b
Description: Availability Zone for AWS Managed MSAD subnet 2
Subnet3Cidr:
Type: String
Default: 10.0.3.0/24
Description: CIDR block for EC2 subnet
Subnet3Az:
Type: String
Default: us-east-1c
Description: Availability Zone for EC2 subnet
WindowsServerAMI:
Type: String
Description: AMI ID for Windows Server
LinuxAMI:
Type: String
Description: AMI ID for Linux Server
InstanceType:
Type: String
Default: t3.medium
Description: EC2 instance type
DomainName:
Type: String
Description: Fully qualified domain name for Microsoft AD
AdminPassword:
Type: String
Description: Admin password for Microsoft AD
NoEcho: true
KeyName:
Type: String
Description: Name of the EC2 Key Pair to use for SSH/RDP access
AllowedPublicIP:
Type: String
Description: Allowed public IP address for RDP and SSH access (e.g., 203.0.113.0/32)
Resources:
MyVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCidr
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: MyVPC
DHCPOptions:
Type: AWS::EC2::DHCPOptions
Properties:
DomainName: !Sub '${DomainName}'
DomainNameServers: !GetAtt MyMicrosoftAd.DnsIpAddresses
VPCDHCPOptionsAssociation:
Type: AWS::EC2::VPCDHCPOptionsAssociation
Properties:
VpcId: !Ref MyVPC
DhcpOptionsId: !Ref DHCPOptions
Subnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref MyVPC
CidrBlock: !Ref Subnet1Cidr
AvailabilityZone: !Ref Subnet1Az
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: MSAD-Subnet1
Subnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref MyVPC
CidrBlock: !Ref Subnet2Cidr
AvailabilityZone: !Ref Subnet2Az
MapPublicIpOnLaunch: false
Tags:
- Key: Name
Value: MSAD-Subnet2
Subnet3:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref MyVPC
CidrBlock: !Ref Subnet3Cidr
AvailabilityZone: !Ref Subnet3Az
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: EC2-Subnet
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: MyInternetGateway
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref MyVPC
InternetGatewayId: !Ref InternetGateway
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref MyVPC
Tags:
- Key: Name
Value: PublicRouteTable
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref Subnet3
RouteTableId: !Ref PublicRouteTable
VPCAllowAllSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow specific IP for RDP and SSH, and VPC internal communication
VpcId: !Ref MyVPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3389
ToPort: 3389
CidrIp: !Ref AllowedPublicIP
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: !Ref AllowedPublicIP
- IpProtocol: -1
CidrIp: !Ref VpcCidr
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: VPCAllowAllSG
MyMicrosoftAd:
Type: AWS::DirectoryService::MicrosoftAD
Properties:
CreateAlias: false
Edition: Standard
EnableSso: false
Name: !Ref DomainName
Password: !Ref AdminPassword
VpcSettings:
SubnetIds:
- !Ref Subnet1
- !Ref Subnet2
VpcId: !Ref MyVPC
WindowsInstanceForADManagement:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
ImageId: !Ref WindowsServerAMI
SubnetId: !Ref Subnet3
KeyName: !Ref KeyName
SecurityGroupIds:
- !Ref VPCAllowAllSG
Tags:
- Key: Name
Value: AD-Management-Server
UserData:
Fn::Base64: !Sub |
<powershell>
tzutil /s "Tokyo Standard Time"
reg add "HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation" /v RealTimeIsUniversal /d 1 /t REG_DWORD /f
Rename-Computer -NewName "ADManagementServer" -Force -Restart
</powershell>
WindowsInstanceForEnterpriseCA:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
ImageId: !Ref WindowsServerAMI
SubnetId: !Ref Subnet3
KeyName: !Ref KeyName
SecurityGroupIds:
- !Ref VPCAllowAllSG
Tags:
- Key: Name
Value: Enterprise-CA-Server
UserData:
Fn::Base64: !Sub |
<powershell>
tzutil /s "Tokyo Standard Time"
reg add "HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation" /v RealTimeIsUniversal /d 1 /t REG_DWORD /f
Rename-Computer -NewName "EnterpriseCAServer" -Force -Restart
</powershell>
LinuxInstance:
Type: AWS::EC2::Instance
Properties:
InstanceType: !Ref InstanceType
ImageId: !Ref LinuxAMI
SubnetId: !Ref Subnet3
KeyName: !Ref KeyName
SecurityGroupIds:
- !Ref VPCAllowAllSG
Tags:
- Key: Name
Value: LinuxServer
UserData:
Fn::Base64: !Sub |
#!/bin/bash
timedatectl set-timezone Asia/Tokyo
hostnamectl set-hostname LinuxServer
Outputs:
VPCId:
Description: The ID of the created VPC
Value: !Ref MyVPC
ADManagementInstance:
Description: Windows Server for AD Management
Value: !Ref WindowsInstanceForADManagement
EnterpriseCAInstance:
Description: Windows Server for Enterprise CA
Value: !Ref WindowsInstanceForEnterpriseCA
LinuxInstance:
Description: Linux Server instance ID
Value: !Ref LinuxInstance
DirectoryId:
Description: The ID of the created Microsoft AD
Value: !Ref MyMicrosoftAd
前回に引き続き、ドメインは yagitest.local
。
DNSアドレスは 10.0.1.206
と 10.0.2.121
です。
2. シェルスクリプト実行用サーバのセットアップ
先ほど作成したLinux Serverでの作業になります。
ec2-userでログインしたところから想定して手順を書きます。
2.1. openldap-clients インストール
# openldap-clientsのインストール
sudo dnf install -y openldap-clients
# インストール確認
ldapsearch -VV
## バージョン情報が表示されれば、インストールは成功
# Microsoft ADへのLDAP接続テスト
ldapsearch -D "yagitest\Admin" -W -H ldap://yagitest.local -b "OU=Users,OU=yagitest,DC=yagitest,DC=local"
## コマンド打鍵後パスワード入力を求められる
## コマンドが正常に通ると、ドメインユーザの詳細一覧が表示される
実際にコマンドを打った時の結果がこちら。
[ec2-user@LinuxServer ~]$ sudo dnf install -y openldap-clients
Updating Subscription Management repositories.
Unable to read consumer identity
This system is not registered with an entitlement server. You can use subscription-manager to register.
Red Hat Enterprise Linux 8 for x86_64 - BaseOS 60 kB/s | 4.1 kB 00:00
Red Hat Ansible Engine 2 for RHEL 8 (RPMs) from 76 kB/s | 4.0 kB 00:00
RHUI Client Configuration Server 8 30 kB/s | 1.5 kB 00:00
Last metadata expiration check: 0:00:01 ago on Mon 09 Dec 2024 09:18:33 PM JST.
Dependencies resolved.
================================================================================
Package Arch Version Repository Size
================================================================================
Installing:
openldap-clients x86_64 2.4.46-20.el8_10 rhel-8-baseos-rhui-rpms 203 k
Transaction Summary
================================================================================
Install 1 Package
Total download size: 203 k
Installed size: 611 k
Downloading Packages:
openldap-clients-2.4.46-20.el8_10.x86_64.rpm 5.2 MB/s | 203 kB 00:00
--------------------------------------------------------------------------------
Total 3.1 MB/s | 203 kB 00:00
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Installing : openldap-clients-2.4.46-20.el8_10.x86_64 1/1
Running scriptlet: openldap-clients-2.4.46-20.el8_10.x86_64 1/1
Verifying : openldap-clients-2.4.46-20.el8_10.x86_64 1/1
Installed products updated.
Installed:
openldap-clients-2.4.46-20.el8_10.x86_64
Complete!
[ec2-user@LinuxServer ~]$
[ec2-user@LinuxServer ~]$
[ec2-user@LinuxServer ~]$ ldapsearch -VV
ldapsearch: @(#) $OpenLDAP: ldapsearch 2.4.46 (Oct 15 2024 19:33:32) $
mockbuild@x86-vm-07.brew-001.prod.iad2.dc.redhat.com:/builddir/build/BUILD/openldap-2.4.46/openldap-2.4.46/clients/tools
(LDAP library: OpenLDAP 20446)
[ec2-user@LinuxServer ~]$
[ec2-user@LinuxServer ~]$
[ec2-user@LinuxServer ~]$ ldapsearch -D "yagitest\Admin" -W -H ldap://IP-C61301AD.yagitest.local -b "OU=Users,OU=yagitest,DC=yagitest,DC=local"
Enter LDAP Password:
# extended LDIF
#
# LDAPv3
# base <OU=Users,OU=yagitest,DC=yagitest,DC=local> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#
# Users, yagitest, yagitest.local
dn: OU=Users,OU=yagitest,DC=yagitest,DC=local
objectClass: top
objectClass: organizationalUnit
ou: Users
distinguishedName: OU=Users,OU=yagitest,DC=yagitest,DC=local
instanceType: 4
whenCreated: 20241207034246.0Z
whenChanged: 20241207034247.0Z
uSNCreated: 12905
uSNChanged: 12913
name: Users
objectGUID:: NoEGx+oUs0i5r4o2i6kzOQ==
systemFlags: -1946157056
objectCategory: CN=Organizational-Unit,CN=Schema,CN=Configuration,DC=yagitest,
DC=local
isCriticalSystemObject: TRUE
dSCorePropagationData: 20241210120041.0Z
dSCorePropagationData: 20241210120041.0Z
dSCorePropagationData: 20241210120041.0Z
dSCorePropagationData: 20241210120041.0Z
dSCorePropagationData: 16010101000000.0Z
# Admin, Users, yagitest, yagitest.local
dn: CN=Admin,OU=Users,OU=yagitest,DC=yagitest,DC=local
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: user
cn: Admin
description: DO NOT DELETE: Provided by AWS for administration of directory o
bjects. This account has FULL CONTROL over the root OU: 'OU=yagitest,DC=yagi
test,DC=local' and group management rights to groups in AWS Delegated Groups
OU
distinguishedName: CN=Admin,OU=Users,OU=yagitest,DC=yagitest,DC=local
instanceType: 4
whenCreated: 20241207034247.0Z
whenChanged: 20241207085945.0Z
displayName: Admin
uSNCreated: 12921
memberOf: CN=AWS Delegated Administrators,OU=AWS Delegated Groups,DC=yagitest,
DC=local
uSNChanged: 14414
name: Admin
objectGUID:: 223HPtnwy0eurFXaBDmW7Q==
userAccountControl: 512
badPwdCount: 0
codePage: 0
countryCode: 0
badPasswordTime: 133780485499589075
lastLogoff: 0
lastLogon: 133783117379190388
pwdLastSet: 133780165673611235
primaryGroupID: 513
objectSid:: AQUAAAAAAAUVAAAAZK+sZFfKWr8TOgOWWAQAAA==
accountExpires: 9223372036854775807
logonCount: 26
sAMAccountName: Admin
sAMAccountType: 805306368
userPrincipalName: admin@yagitest.local
objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=yagitest,DC=local
dSCorePropagationData: 20241207034303.0Z
dSCorePropagationData: 20241207034302.0Z
dSCorePropagationData: 20241207034259.0Z
dSCorePropagationData: 20241207034259.0Z
dSCorePropagationData: 16010714223649.0Z
lastLogonTimestamp: 133780355851740912
# search result
search: 2
result: 0 Success
# numResponses: 3
# numEntries: 2
2.2 Microsoft ADにLDAPS通信する
まず、前回エクスポートした証明書 LDAP-root-CA.cer
をLinux Serverの /tmp
に配置します。
[ec2-user@LinuxServer tmp]$ ls -l /tmp
-rw-r--r--. 1 ec2-user ec2-user 1286 Dec 8 12:47 LDAP-root-CA.cer
そこから、以下のコマンドを叩いていきます。
# 証明書を.pemファイルに変換
openssl x509 -in /tmp/LDAP-root-CA.cer -out /tmp/LDAP-root-CA.pem
# 証明書をLDAP クライアントの証明書ストアに配置
sudo cp /tmp/LDAP-root-CA.pem /etc/openldap/certs
# LDAP クライアントの設定に証明書のパスを記述
sudo vi /etc/openldap/ldap.conf
## 末尾に以下を追記して保存
# ======
# Specify the path to the TLS CA certificate
# Used by OpenLDAP to establish trusted TLS connections with the AD domain 'yagitest.local'
TLS_CACERT /etc/openldap/certs/LDAP-root-CA.pem
# ======
# Microsoft ADへのLDAPS接続テスト(Adminで接続)
ldapsearch -D "yagitest\Admin" -W -H ldaps://yagitest.local -b "OU=Users,OU=yagitest,DC=yagitest,DC=local"
## コマンド打鍵後パスワード入力を求められる
## コマンドが正常に通ると、ドメインユーザの詳細一覧が表示される
3. シェルスクリプトを作成する
以下のようにシェルスクリプトを作成しました。
#!/bin/bash
if [ "$#" -ne 3 ]; then
echo "Usage: $0 <username> <old_password> <new_password>"
exit 1
fi
USERNAME="$1"
OLD_PASSWORD="$2"
NEW_PASSWORD="$3"
# LDAPサーバーの設定
PRIMARY_SERVER_IP="10.0.1.206" # ここに1つ目のIPアドレスを設定
SECONDARY_SERVER_IP="10.0.2.121" # ここに2つ目のIPアドレスを設定
DEFAULT_HOST="yagitest.local"
# 接続先の決定
if host "$DEFAULT_HOST" >/dev/null 2>&1; then
SERVER_HOST="$DEFAULT_HOST"
echo "Using hostname: $SERVER_HOST"
else
echo "Warning: Unable to resolve hostname $DEFAULT_HOST"
echo "Trying primary IP address: $PRIMARY_SERVER_IP"
if ping -c 1 "$PRIMARY_SERVER_IP" >/dev/null 2>&1; then
SERVER_HOST="$PRIMARY_SERVER_IP"
else
echo "Primary IP unreachable, trying secondary IP: $SECONDARY_SERVER_IP"
if ping -c 1 "$SECONDARY_SERVER_IP" >/dev/null 2>&1; then
SERVER_HOST="$SECONDARY_SERVER_IP"
else
echo "Error: Neither hostname nor IP addresses are accessible"
exit 1
fi
fi
fi
LDAP_SERVER="ldaps://$SERVER_HOST"
LDAP_BIND_DN="CN=$USERNAME,OU=Users,OU=yagitest,DC=yagitest,DC=local"
TMP_LDIF_FILE=$(mktemp)
trap 'rm -f "$TMP_LDIF_FILE"; exit $RESULT' EXIT # スクリプト終了時に一時ファイルを削除し、適切な終了コードを返す
# 一時ファイルの作成に失敗した場合
if [ ! -f "$TMP_LDIF_FILE" ]; then
echo "Error: Failed to create temporary file"
RESULT=1
exit 1
fi
cat <<EOF > "$TMP_LDIF_FILE"
dn: $LDAP_BIND_DN
changetype: modify
replace: unicodePwd
unicodePwd:: $(echo -n "\"$NEW_PASSWORD\"" | iconv -f UTF-8 -t UTF-16LE | base64)
EOF
# iconvコマンドが失敗した場合のエラーチェック
if [ $? -ne 0 ]; then
echo "Error: Failed to encode password"
RESULT=1
exit 1
fi
# LDAPサーバーへの接続とパスワード変更を試行
ldapmodify -H "$LDAP_SERVER" -D "$LDAP_BIND_DN" -w "$OLD_PASSWORD" -f "$TMP_LDIF_FILE"
RESULT=$?
if [ $RESULT -eq 0 ]; then
echo "Password changed successfully for $USERNAME on server $SERVER_HOST"
exit 0
else
echo "Failed to change password for $USERNAME on server $SERVER_HOST. Please check the logs."
exit 1
fi
3.1. 使い方
まず、シェルスクリプト実行用サーバの適当なパスにシェルスクリプトを配置します。
私は /opt/script/ad_password_changer.sh
としました。
その際、シェルスクリプト内の変数を環境に応じて修正してください。
# LDAPサーバーの設定
PRIMARY_SERVER_IP="10.0.1.206" # ここに1つ目のIPアドレスを設定
SECONDARY_SERVER_IP="10.0.2.121" # ここに2つ目のIPアドレスを設定
DEFAULT_HOST="yagitest.local"
~~略~~
LDAP_BIND_DN="CN=$USERNAME,OU=Users,OU=yagitest,DC=yagitest,DC=local"
実行時に指定する引数はこんな感じ。
- 第1引数:パスワードを変更する対象のユーザ名(ドメイン指定は不要)
- 第2引数:パスワード変更対象ユーザの現在のパスワード
- 第3引数:パスワード変更対象ユーザの新しいパスワード
3.2. 補足
- 万一のDNSエラーでコケたくないので、IPアドレスでも接続できるようにしました。
- パスワードの設定をいじるためにはLDAPS接続(
ldaps://$SERVER_HOST
)が必須のようです。
※ldap://$SERVER_HOST
にするとエラーになりました。
やってみる
Adminのパスワード変更をしてみます。
[ec2-user@LinuxServer ~]$ /opt/script/ad_password_changer.sh Admin '<現在のパスワード>' '<新しいパスワード>'
Using hostname: IP-C61301AD.yagitest.local
modifying entry "CN=Admin,OU=Users,OU=yagitest,DC=yagitest,DC=local"
Password changed successfully for Admin on server IP-C61301AD.yagitest.local
うまくいきました!
AD管理用サーバへAdminでRDPしてみて、変更後のパスワードでログインできることを確認しておきましょう。
ちなみに、誤ったパスワードを打ち込むとこうなります。
[ec2-user@LinuxServer ~]$ /opt/script/ad_password_changer.sh Admin '<誤ったパスワード>' '<新しいパスワード>'
Using hostname: IP-C61301AD.yagitest.local
ldap_bind: Invalid credentials (49)
additional info: 80090308: LdapErr: DSID-0C09050F, comment: AcceptSecurityContext error, data 52e, v4563
Failed to change password for Admin on server IP-C61301AD.yagitest.local. Please check the logs.
Invalid credentials
と教えてくれましたね。
ちなみにちなみに、新しいパスワードとして、パスワードポリシーを満たさない文字列(123
など)を入れるとこうなりました。
[ec2-user@LinuxServer ~]$ /opt/script/ad_password_changer.sh Admin '<現在のパスワード>' '<パスワードポリシーを満たさないパスワード>'
Using hostname: IP-C61301AD.yagitest.local
modifying entry "CN=Admin,OU=Users,OU=yagitest,DC=yagitest,DC=local"
ldap_modify: Server is unwilling to perform (53)
additional info: 0000052D: SvcErr: DSID-031A124C, problem 5003 (WILL_NOT_PERFORM), data 0
Failed to change password for Admin on server IP-C61301AD.yagitest.local. Please check the logs.
Server is unwilling to perform
とのことで、パスワード変更が失敗します。
終わりに
ドメインユーザのパスワードを、LDAPSを用いたシェルスクリプトを用いて変更してみました。
これが何の役に立つのか分からないですが、どなたかの役に立てば幸いです
参考文献