1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Japan AWS Top EngineersAdvent Calendar 2024

Day 19

【LDAPS】シェルスクリプトでActive Directoryユーザーのパスワードを安全に変更する方法

Last updated at Posted at 2024-12-17

はじめに

お疲れ様です。矢儀 @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を許可するようにしていますが、扱いにはご注意ください。

vpc-msad-ec2-setup.yaml
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.20610.0.2.121 です。
image.png

EC2は以下の3台構成になります。
image.png

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. シェルスクリプトを作成する

以下のようにシェルスクリプトを作成しました。

ad_password_changer.sh
#!/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 とのことで、パスワード変更が失敗します。

ユーザのパスワード期限が切れて↓のような状態になっていたら、このスクリプトによるパスワード変更はできませんでした。。
image.png

終わりに

ドメインユーザのパスワードを、LDAPSを用いたシェルスクリプトを用いて変更してみました。
これが何の役に立つのか分からないですが、どなたかの役に立てば幸いです

参考文献

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?