FreeIPA自助重置密码

Table of contents

背景

安裝

  • pip install 三個名稱,結果都是一樣:python-freeipa(官網)、ipapython、python-ipa(GPT’s)

python-freeipa測試

  • python-freeipa與新版FreeIPA的CA管理並未銜接得很好,因此需要在ipython中逐一詳細測試,確定可以連接、登入、並執行密碼修改。
  • 有關url的設定:不需要前綴(http或https)、domain name與ip的效果式一樣。
  • 有關CA認證與SSL設定
    • FreeIPA需使用https才能連接。目前公司並未開放個人電腦自己新增證書。
    • 傳統apache是用/etc/httpd/alias/httpd.crt/etc/httpd/alias/httpd.key
    • 但是新版的FreeIPA的alias(設定如下)是使用db形式來授權,因此須關閉SSL的驗證,這在python-freeipa默認是True,需要在python中更改:verify_ssl=False
sudo ls /etc/httpd/alias
lrwxrwxrwx. 1 root root     24 Feb  5 14:20 libnssckbi.so -> /usr/lib64/libnssckbi.so*
-rw-------. 1 root root   5.2K Feb  5 14:20 install.log
-rw-------. 1 root root     32 Feb  5 17:56 ipasession.key
-rw-------. 1 root apache   41 Feb  8 15:38 pwdfile.txt
-rw-r-----. 1 root apache  16K Feb  8 15:38 secmod.db
-rw-r-----. 1 root apache  16K Feb  8 15:38 key3.db
-rw-r-----. 1 root apache  64K Feb  8 15:38 cert8.db
  • 是否需先登入:
    • 傳統GPT與claude都建議先以admin或使用者自己舊的帳密先登入,確認無誤再開放密碼的修改。前者沒有必要、且有帳密暴露之虞。
    • 事實上,client.change_password()函式的錯誤訊息包括了登入錯誤,因此似乎不需要先登入、確認後再接受密碼修改。
  • 如何呈現錯誤訊息
    • 因為錯誤可能有很多種(使用者不存在、舊密碼不對、太快修改密碼等等),傳統引到錯誤訊息html檔的作法太過繁瑣。
    • 還好python-freeipa的錯誤訊息是個html的內容,可以直接寫成error.html讓flask程式呼叫。

Flask python

  • 3個路由
    • 其中第2個/change_password,還是回到index.html
    • 還是會需要特別的/change_password路由,讓html來呼叫,呼叫/感覺會是個endless loop。
    • 沒有特別一個error的路由,因為是用現成的python-freeipa error message。
  • request.form、還是request.get_json,可以詳見這一篇,可能跟html的設計有關,如果是分開的空格,還是前者比較方便。
from flask import Flask, request, render_template, redirect, url_for
from python_freeipa import ClientMeta

app = Flask(__name__)
url='node03.sinotech-eng.com'

@app.route('/')
def index():
    return render_template('index.html')

@app.route("/change_password", methods=["GET","POST"])
def change_password():
    if request.method == 'POST':
        client = ClientMeta(url,verify_ssl=False)
        username = request.form['username']
        old_password = request.form['old_password']
        new_password = request.form['new_password']
        try:
            client.change_password(username, new_password, old_password)
            return redirect(url_for('success'))
        except Exception as e:
            with open('templates/error.html','w') as f:
                f.write(str(e))
            return render_template('error.html', error=str(e))
    return render_template("index.html")

# 密碼更改成功路由
@app.route('/success')
def success():
    return render_template('success.html')


if __name__ == "__main__":
    app.run(debug=True, host=url, port=5000)

templates資料夾

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>更改密碼</title>
</head>
<body>
    <h1>歡迎來到FreeIPA LDAP密碼更改網站</h1>
    \{\% if error \%\}
        <p style="color: red;"></p>
    \{\% endif \%\}
    \{\% with messages = get_flashed_messages() \%\}
        \{\% if messages \%\}
            <ul>
                \{\% for message in messages \%\}
                    <li></li>
                \{\% endfor \%\}
            </ul>
        \{\% endif \%\}
    \{\% endwith \%\}
    <form action="" method="post">
        <label for="username">使用者名稱:</label>
        <input type="text" name="username" required><br>
        <label for="old_password">舊密碼:</label>
        <input type="password" name="old_password" required><br>
        <label for="new_password">新密碼:</label>
        <input type="password" name="new_password" required><br>
        <button type="submit">Change Password</button>
    </form>
</body>
</html>

success.html

<!DOCTYPE html>
<html>
<head>
    <title>密碼更改成功</title>
</head>
<body>
    <h1>密碼更改成功!</h1>
    <a href="">返回首頁</a>
</body>
</html>