diff --git a/Cargo.lock b/Cargo.lock
index 729925cc2f3ff..bbb9b9eebda9d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3383,6 +3383,7 @@ dependencies = [
  "opendal",
  "ordered-float 4.2.0",
  "parking_lot 0.12.1",
+ "passwords",
  "percent-encoding",
  "regex",
  "roaring",
@@ -9510,6 +9511,15 @@ dependencies = [
  "regex",
 ]
 
+[[package]]
+name = "passwords"
+version = "3.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11407193a7c2bd14ec6b0ec3394da6fdcf7a4d5dcbc8c3cc38dfb17802c8d59c"
+dependencies = [
+ "random-pick",
+]
+
 [[package]]
 name = "paste"
 version = "1.0.12"
@@ -10620,6 +10630,37 @@ dependencies = [
  "rand_core 0.6.4",
 ]
 
+[[package]]
+name = "random-number"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a3da5cbb4c27c5150c03a54a7e4745437cd90f9e329ae657c0b889a144bb7be"
+dependencies = [
+ "proc-macro-hack",
+ "rand 0.8.5",
+ "random-number-macro-impl",
+]
+
+[[package]]
+name = "random-number-macro-impl"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b86292cf41ccfc96c5de7165c1c53d5b4ac540c5bab9d1857acbe9eba5f1a0b"
+dependencies = [
+ "proc-macro-hack",
+ "quote",
+ "syn 2.0.29",
+]
+
+[[package]]
+name = "random-pick"
+version = "1.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c179499072da789afe44127d5f4aa6012de2c2f96ef759990196b37387a2a0f8"
+dependencies = [
+ "random-number",
+]
+
 [[package]]
 name = "raw-cpuid"
 version = "10.7.0"
diff --git a/src/common/exception/src/exception_code.rs b/src/common/exception/src/exception_code.rs
index 137caee6372c7..350fda73bdebc 100644
--- a/src/common/exception/src/exception_code.rs
+++ b/src/common/exception/src/exception_code.rs
@@ -225,6 +225,11 @@ build_exceptions! {
     NetworkPolicyAlreadyExists(2208),
     IllegalNetworkPolicy(2209),
     NetworkPolicyIsUsedByUser(2210),
+    UnknownPasswordPolicy(2211),
+    PasswordPolicyAlreadyExists(2212),
+    IllegalPasswordPolicy(2213),
+    PasswordPolicyIsUsedByUser(2214),
+    InvalidPassword(2215),
 
     // Meta api error codes.
     DatabaseAlreadyExists(2301),
diff --git a/src/meta/app/src/principal/mod.rs b/src/meta/app/src/principal/mod.rs
index d4a137c72e179..5fb35d7b67962 100644
--- a/src/meta/app/src/principal/mod.rs
+++ b/src/meta/app/src/principal/mod.rs
@@ -18,6 +18,7 @@ mod connection;
 mod file_format;
 mod network_policy;
 mod ownership_info;
+mod password_policy;
 mod principal_identity;
 mod role_info;
 mod user_auth;
@@ -35,6 +36,7 @@ pub use connection::*;
 pub use file_format::*;
 pub use network_policy::NetworkPolicy;
 pub use ownership_info::OwnershipInfo;
+pub use password_policy::PasswordPolicy;
 pub use principal_identity::PrincipalIdentity;
 pub use role_info::RoleInfo;
 pub use role_info::RoleInfoSerdeError;
diff --git a/src/meta/app/src/principal/password_policy.rs b/src/meta/app/src/principal/password_policy.rs
new file mode 100644
index 0000000000000..7b22e64c75e31
--- /dev/null
+++ b/src/meta/app/src/principal/password_policy.rs
@@ -0,0 +1,35 @@
+// Copyright 2021 Datafuse Labs
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use chrono::DateTime;
+use chrono::Utc;
+
+#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Eq, PartialEq, Default)]
+pub struct PasswordPolicy {
+    pub name: String,
+    pub min_length: u64,
+    pub max_length: u64,
+    pub min_upper_case_chars: u64,
+    pub min_lower_case_chars: u64,
+    pub min_numeric_chars: u64,
+    pub min_special_chars: u64,
+    pub min_age_days: u64,
+    pub max_age_days: u64,
+    pub max_retries: u64,
+    pub lockout_time_mins: u64,
+    pub history: u64,
+    pub comment: String,
+    pub create_on: DateTime<Utc>,
+    pub update_on: Option<DateTime<Utc>>,
+}
diff --git a/src/meta/app/src/principal/user_info.rs b/src/meta/app/src/principal/user_info.rs
index 6f8d31f12a1cb..8906b339cebb2 100644
--- a/src/meta/app/src/principal/user_info.rs
+++ b/src/meta/app/src/principal/user_info.rs
@@ -107,6 +107,8 @@ pub struct UserOption {
     default_role: Option<String>,
 
     network_policy: Option<String>,
+
+    password_policy: Option<String>,
 }
 
 impl UserOption {
@@ -115,6 +117,7 @@ impl UserOption {
             flags,
             default_role: None,
             network_policy: None,
+            password_policy: None,
         }
     }
 
@@ -137,6 +140,11 @@ impl UserOption {
         self
     }
 
+    pub fn with_password_policy(mut self, password_policy: Option<String>) -> Self {
+        self.password_policy = password_policy;
+        self
+    }
+
     pub fn with_set_flag(mut self, flag: UserOptionFlag) -> Self {
         self.flags.insert(flag);
         self
@@ -154,6 +162,10 @@ impl UserOption {
         self.network_policy.as_ref()
     }
 
+    pub fn password_policy(&self) -> Option<&String> {
+        self.password_policy.as_ref()
+    }
+
     pub fn set_default_role(&mut self, default_role: Option<String>) {
         self.default_role = default_role;
     }
@@ -162,6 +174,10 @@ impl UserOption {
         self.network_policy = network_policy;
     }
 
+    pub fn set_password_policy(&mut self, password_policy: Option<String>) {
+        self.password_policy = password_policy;
+    }
+
     pub fn set_all_flag(&mut self) {
         self.flags = BitFlags::all();
     }
diff --git a/src/meta/proto-conv/src/user_from_to_protobuf_impl.rs b/src/meta/proto-conv/src/user_from_to_protobuf_impl.rs
index 44e82bd93ffea..97580c447d174 100644
--- a/src/meta/proto-conv/src/user_from_to_protobuf_impl.rs
+++ b/src/meta/proto-conv/src/user_from_to_protobuf_impl.rs
@@ -99,7 +99,8 @@ impl FromToProto for mt::principal::UserOption {
         Ok(mt::principal::UserOption::default()
             .with_flags(flags)
             .with_default_role(p.default_role)
-            .with_network_policy(p.network_policy))
+            .with_network_policy(p.network_policy)
+            .with_password_policy(p.password_policy))
     }
 
     fn to_pb(&self) -> Result<pb::UserOption, Incompatible> {
@@ -109,6 +110,7 @@ impl FromToProto for mt::principal::UserOption {
             flags: self.flags().bits(),
             default_role: self.default_role().cloned(),
             network_policy: self.network_policy().cloned(),
+            password_policy: self.password_policy().cloned(),
         })
     }
 }
@@ -389,3 +391,59 @@ impl FromToProto for mt::principal::NetworkPolicy {
         })
     }
 }
+
+impl FromToProto for mt::principal::PasswordPolicy {
+    type PB = pb::PasswordPolicy;
+    fn get_pb_ver(p: &Self::PB) -> u64 {
+        p.ver
+    }
+    fn from_pb(p: pb::PasswordPolicy) -> Result<Self, Incompatible>
+    where Self: Sized {
+        reader_check_msg(p.ver, p.min_reader_ver)?;
+        Ok(mt::principal::PasswordPolicy {
+            name: p.name.clone(),
+            min_length: p.min_length,
+            max_length: p.max_length,
+            min_upper_case_chars: p.min_upper_case_chars,
+            min_lower_case_chars: p.min_lower_case_chars,
+            min_numeric_chars: p.min_numeric_chars,
+            min_special_chars: p.min_special_chars,
+            min_age_days: p.min_age_days,
+            max_age_days: p.max_age_days,
+            max_retries: p.max_retries,
+            lockout_time_mins: p.lockout_time_mins,
+            history: p.history,
+            comment: p.comment,
+            create_on: DateTime::<Utc>::from_pb(p.create_on)?,
+            update_on: match p.update_on {
+                Some(t) => Some(DateTime::<Utc>::from_pb(t)?),
+                None => None,
+            },
+        })
+    }
+
+    fn to_pb(&self) -> Result<pb::PasswordPolicy, Incompatible> {
+        Ok(pb::PasswordPolicy {
+            ver: VER,
+            min_reader_ver: MIN_READER_VER,
+            name: self.name.clone(),
+            min_length: self.min_length,
+            max_length: self.max_length,
+            min_upper_case_chars: self.min_upper_case_chars,
+            min_lower_case_chars: self.min_lower_case_chars,
+            min_numeric_chars: self.min_numeric_chars,
+            min_special_chars: self.min_special_chars,
+            min_age_days: self.min_age_days,
+            max_age_days: self.max_age_days,
+            max_retries: self.max_retries,
+            lockout_time_mins: self.lockout_time_mins,
+            history: self.history,
+            comment: self.comment.clone(),
+            create_on: self.create_on.to_pb()?,
+            update_on: match &self.update_on {
+                Some(t) => Some(t.to_pb()?),
+                None => None,
+            },
+        })
+    }
+}
diff --git a/src/meta/proto-conv/src/util.rs b/src/meta/proto-conv/src/util.rs
index f4b2b6299c849..b69d88460e4b9 100644
--- a/src/meta/proto-conv/src/util.rs
+++ b/src/meta/proto-conv/src/util.rs
@@ -96,6 +96,7 @@ const META_CHANGE_LOG: &[(u64, &str)] = &[
     (64, "2023-11-16: Add: user.proto/NDJsonFileFormatParams add field `missing_field_as` and `null_field_as`", ),
     (65, "2023-11-16: Retype: use Datetime<Utc> instead of u64 to in lvt.time", ),
     (66, "2023-12-15: Add: stage.proto/StageInfo::created_on", ),
+    (67, "2023-12-19: Add: user.proto/PasswordPolicy and UserOption::password_policy", ),
     // Dear developer:
     //      If you're gonna add a new metadata version, you'll have to add a test for it.
     //      You could just copy an existing test file(e.g., `../tests/it/v024_table_meta.rs`)
diff --git a/src/meta/proto-conv/tests/it/main.rs b/src/meta/proto-conv/tests/it/main.rs
index 3bdd6e8eafa82..90ace49427979 100644
--- a/src/meta/proto-conv/tests/it/main.rs
+++ b/src/meta/proto-conv/tests/it/main.rs
@@ -70,3 +70,4 @@ mod v063_connection;
 mod v064_ndjson_format_params;
 mod v065_least_visible_time;
 mod v066_stage_create_on;
+mod v067_password_policy;
diff --git a/src/meta/proto-conv/tests/it/v067_password_policy.rs b/src/meta/proto-conv/tests/it/v067_password_policy.rs
new file mode 100644
index 0000000000000..3671c39ef67c7
--- /dev/null
+++ b/src/meta/proto-conv/tests/it/v067_password_policy.rs
@@ -0,0 +1,110 @@
+// Copyright 2021 Datafuse Labs
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::collections::HashSet;
+
+use chrono::TimeZone;
+use chrono::Utc;
+use databend_common_meta_app::principal::UserPrivilegeType;
+use enumflags2::make_bitflags;
+use minitrace::func_name;
+
+use crate::common;
+
+// These bytes are built when a new version in introduced,
+// and are kept for backward compatibility test.
+//
+// *************************************************************
+// * These messages should never be updated,                   *
+// * only be added when a new version is added,                *
+// * or be removed when an old version is no longer supported. *
+// *************************************************************
+//
+// The message bytes are built from the output of `test_build_pb_buf()`
+#[test]
+fn test_decode_v67_password_policy() -> anyhow::Result<()> {
+    // password policy
+    let bytes: Vec<u8> = vec![
+        10, 19, 116, 101, 115, 116, 112, 97, 115, 115, 119, 111, 114, 100, 112, 111, 108, 105, 99,
+        121, 49, 16, 8, 24, 30, 32, 1, 40, 2, 48, 3, 56, 4, 64, 10, 72, 90, 80, 5, 88, 10, 96, 1,
+        106, 12, 115, 111, 109, 101, 32, 99, 111, 109, 109, 101, 110, 116, 114, 23, 50, 48, 49, 52,
+        45, 49, 49, 45, 50, 56, 32, 49, 50, 58, 48, 48, 58, 48, 57, 32, 85, 84, 67, 122, 23, 50,
+        48, 49, 52, 45, 49, 49, 45, 50, 56, 32, 49, 50, 58, 48, 48, 58, 48, 57, 32, 85, 84, 67,
+        160, 6, 67, 168, 6, 24,
+    ];
+
+    let want = || databend_common_meta_app::principal::PasswordPolicy {
+        name: "testpasswordpolicy1".to_string(),
+        min_length: 8,
+        max_length: 30,
+        min_upper_case_chars: 1,
+        min_lower_case_chars: 2,
+        min_numeric_chars: 3,
+        min_special_chars: 4,
+        min_age_days: 10,
+        max_age_days: 90,
+        max_retries: 5,
+        lockout_time_mins: 10,
+        history: 1,
+        comment: "some comment".to_string(),
+        create_on: Utc.with_ymd_and_hms(2014, 11, 28, 12, 0, 9).unwrap(),
+        update_on: Some(Utc.with_ymd_and_hms(2014, 11, 28, 12, 0, 9).unwrap()),
+    };
+
+    common::test_pb_from_to(func_name!(), want())?;
+    common::test_load_old(func_name!(), bytes.as_slice(), 67, want())?;
+
+    // user info with password policy
+    let bytes: Vec<u8> = vec![
+        10, 9, 116, 101, 115, 116, 95, 117, 115, 101, 114, 18, 1, 37, 26, 25, 18, 17, 10, 13, 116,
+        101, 115, 116, 95, 112, 97, 115, 115, 119, 111, 114, 100, 16, 1, 160, 6, 67, 168, 6, 24,
+        34, 26, 10, 18, 10, 8, 10, 0, 160, 6, 67, 168, 6, 24, 16, 2, 160, 6, 67, 168, 6, 24, 160,
+        6, 67, 168, 6, 24, 42, 15, 8, 10, 16, 128, 80, 24, 128, 160, 1, 160, 6, 67, 168, 6, 24, 50,
+        46, 8, 1, 18, 5, 114, 111, 108, 101, 49, 26, 8, 109, 121, 112, 111, 108, 105, 99, 121, 34,
+        19, 116, 101, 115, 116, 112, 97, 115, 115, 119, 111, 114, 100, 112, 111, 108, 105, 99, 121,
+        49, 160, 6, 67, 168, 6, 24, 160, 6, 67, 168, 6, 24,
+    ];
+
+    let want = || databend_common_meta_app::principal::UserInfo {
+        name: "test_user".to_string(),
+        hostname: "%".to_string(),
+        auth_info: databend_common_meta_app::principal::AuthInfo::Password {
+            hash_value: [
+                116, 101, 115, 116, 95, 112, 97, 115, 115, 119, 111, 114, 100,
+            ]
+            .to_vec(),
+            hash_method: databend_common_meta_app::principal::PasswordHashMethod::DoubleSha1,
+        },
+        grants: databend_common_meta_app::principal::UserGrantSet::new(
+            vec![databend_common_meta_app::principal::GrantEntry::new(
+                databend_common_meta_app::principal::GrantObject::Global,
+                make_bitflags!(UserPrivilegeType::{Create}),
+            )],
+            HashSet::new(),
+        ),
+        quota: databend_common_meta_app::principal::UserQuota {
+            max_cpu: 10,
+            max_memory_in_bytes: 10240,
+            max_storage_in_bytes: 20480,
+        },
+        option: databend_common_meta_app::principal::UserOption::default()
+            .with_set_flag(databend_common_meta_app::principal::UserOptionFlag::TenantSetting)
+            .with_default_role(Some("role1".into()))
+            .with_network_policy(Some("mypolicy".to_string()))
+            .with_password_policy(Some("testpasswordpolicy1".to_string())),
+    };
+
+    common::test_pb_from_to(func_name!(), want())?;
+    common::test_load_old(func_name!(), bytes.as_slice(), 67, want())
+}
diff --git a/src/meta/protos/proto/user.proto b/src/meta/protos/proto/user.proto
index b5cc639d33391..b7f5408b4bd49 100644
--- a/src/meta/protos/proto/user.proto
+++ b/src/meta/protos/proto/user.proto
@@ -106,6 +106,7 @@ message UserOption {
   uint64 flags = 1;
   optional string default_role = 2;
   optional string network_policy = 3;
+  optional string password_policy = 4;
 }
 
 message UserInfo {
@@ -139,3 +140,24 @@ message NetworkPolicy {
   string create_on = 5;
   optional string update_on = 6;
 }
+
+message PasswordPolicy {
+  uint64 ver = 100;
+  uint64 min_reader_ver = 101;
+
+  string name = 1;
+  uint64 min_length = 2;
+  uint64 max_length = 3;
+  uint64 min_upper_case_chars = 4;
+  uint64 min_lower_case_chars = 5;
+  uint64 min_numeric_chars = 6;
+  uint64 min_special_chars = 7;
+  uint64 min_age_days = 8;
+  uint64 max_age_days = 9;
+  uint64 max_retries = 10;
+  uint64 lockout_time_mins = 11;
+  uint64 history = 12;
+  string comment = 13;
+  string create_on = 14;
+  optional string update_on = 15;
+}
diff --git a/src/query/ast/src/ast/format/ast_format.rs b/src/query/ast/src/ast/format/ast_format.rs
index dc471b28839f4..b924fd941a5fe 100644
--- a/src/query/ast/src/ast/format/ast_format.rs
+++ b/src/query/ast/src/ast/format/ast_format.rs
@@ -2604,6 +2604,50 @@ impl<'ast> Visitor<'ast> for AstFormatVisitor {
         self.children.push(node);
     }
 
+    fn visit_create_password_policy(&mut self, stmt: &'ast CreatePasswordPolicyStmt) {
+        let ctx = AstFormatContext::new(format!("PasswordPolicyName {}", stmt.name));
+        let child = FormatTreeNode::new(ctx);
+
+        let name = "CreatePasswordPolicy".to_string();
+        let format_ctx = AstFormatContext::with_children(name, 1);
+        let node = FormatTreeNode::with_children(format_ctx, vec![child]);
+        self.children.push(node);
+    }
+
+    fn visit_alter_password_policy(&mut self, stmt: &'ast AlterPasswordPolicyStmt) {
+        let ctx = AstFormatContext::new(format!("PasswordPolicyName {}", stmt.name));
+        let child = FormatTreeNode::new(ctx);
+
+        let name = "AlterPasswordPolicy".to_string();
+        let format_ctx = AstFormatContext::with_children(name, 1);
+        let node = FormatTreeNode::with_children(format_ctx, vec![child]);
+        self.children.push(node);
+    }
+
+    fn visit_drop_password_policy(&mut self, stmt: &'ast DropPasswordPolicyStmt) {
+        let ctx = AstFormatContext::new(format!("PasswordPolicyName {}", stmt.name));
+        let child = FormatTreeNode::new(ctx);
+
+        let name = "DropPasswordPolicy".to_string();
+        let format_ctx = AstFormatContext::with_children(name, 1);
+        let node = FormatTreeNode::with_children(format_ctx, vec![child]);
+        self.children.push(node);
+    }
+
+    fn visit_desc_password_policy(&mut self, stmt: &'ast DescPasswordPolicyStmt) {
+        let ctx = AstFormatContext::new(format!("PasswordPolicyName {}", stmt.name));
+        let child = FormatTreeNode::new(ctx);
+
+        let name = "DescPasswordPolicy".to_string();
+        let format_ctx = AstFormatContext::with_children(name, 1);
+        let node = FormatTreeNode::with_children(format_ctx, vec![child]);
+        self.children.push(node);
+    }
+
+    fn visit_show_password_policies(&mut self, show_options: &'ast Option<ShowOptions>) {
+        self.visit_show_options(show_options, "ShowPasswordPolicies".to_string());
+    }
+
     fn visit_with(&mut self, with: &'ast With) {
         let mut children = Vec::with_capacity(with.ctes.len());
         for cte in with.ctes.iter() {
diff --git a/src/query/ast/src/ast/statements/mod.rs b/src/query/ast/src/ast/statements/mod.rs
index dd550cf0c3d00..a0482e2f349b1 100644
--- a/src/query/ast/src/ast/statements/mod.rs
+++ b/src/query/ast/src/ast/statements/mod.rs
@@ -28,6 +28,7 @@ mod kill;
 mod lock;
 mod merge_into;
 mod network_policy;
+mod password_policy;
 mod pipe;
 mod presign;
 mod replace;
@@ -61,6 +62,7 @@ pub use kill::*;
 pub use lock::*;
 pub use merge_into::*;
 pub use network_policy::*;
+pub use password_policy::*;
 pub use pipe::*;
 pub use presign::*;
 pub use replace::*;
diff --git a/src/query/ast/src/ast/statements/password_policy.rs b/src/query/ast/src/ast/statements/password_policy.rs
new file mode 100644
index 0000000000000..b1e51c51893da
--- /dev/null
+++ b/src/query/ast/src/ast/statements/password_policy.rs
@@ -0,0 +1,234 @@
+// Copyright 2021 Datafuse Labs
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::fmt::Display;
+use std::fmt::Formatter;
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct CreatePasswordPolicyStmt {
+    pub if_not_exists: bool,
+    pub name: String,
+    pub set_options: PasswordSetOptions,
+}
+
+impl Display for CreatePasswordPolicyStmt {
+    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
+        write!(f, "CREATE PASSWORD POLICY ")?;
+        if self.if_not_exists {
+            write!(f, "IF NOT EXISTS ")?;
+        }
+        write!(f, "{}", self.name)?;
+        write!(f, "{}", self.set_options)?;
+
+        Ok(())
+    }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct AlterPasswordPolicyStmt {
+    pub if_exists: bool,
+    pub name: String,
+    pub action: AlterPasswordAction,
+}
+
+impl Display for AlterPasswordPolicyStmt {
+    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
+        write!(f, "ALTER PASSWORD POLICY ")?;
+        if self.if_exists {
+            write!(f, "IF EXISTS ")?;
+        }
+        write!(f, "{} ", self.name)?;
+        write!(f, "{}", self.action)?;
+
+        Ok(())
+    }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub enum AlterPasswordAction {
+    SetOptions(PasswordSetOptions),
+    UnSetOptions(PasswordUnSetOptions),
+}
+
+impl Display for AlterPasswordAction {
+    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
+        match self {
+            AlterPasswordAction::SetOptions(set_options) => {
+                write!(f, "SET {}", set_options)?;
+            }
+            AlterPasswordAction::UnSetOptions(unset_options) => {
+                write!(f, "UNSET {}", unset_options)?;
+            }
+        }
+
+        Ok(())
+    }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct PasswordSetOptions {
+    pub min_length: Option<u64>,
+    pub max_length: Option<u64>,
+    pub min_upper_case_chars: Option<u64>,
+    pub min_lower_case_chars: Option<u64>,
+    pub min_numeric_chars: Option<u64>,
+    pub min_special_chars: Option<u64>,
+    pub min_age_days: Option<u64>,
+    pub max_age_days: Option<u64>,
+    pub max_retries: Option<u64>,
+    pub lockout_time_mins: Option<u64>,
+    pub history: Option<u64>,
+    pub comment: Option<String>,
+}
+
+impl Display for PasswordSetOptions {
+    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
+        if let Some(min_length) = self.min_length {
+            write!(f, " PASSWORD_MIN_LENGTH = {}", min_length)?;
+        }
+        if let Some(max_length) = self.max_length {
+            write!(f, " PASSWORD_MAX_LENGTH = {}", max_length)?;
+        }
+        if let Some(min_upper_case_chars) = self.min_upper_case_chars {
+            write!(
+                f,
+                " PASSWORD_MIN_UPPER_CASE_CHARS = {}",
+                min_upper_case_chars
+            )?;
+        }
+        if let Some(min_lower_case_chars) = self.min_lower_case_chars {
+            write!(
+                f,
+                " PASSWORD_MIN_LOWER_CASE_CHARS = {}",
+                min_lower_case_chars
+            )?;
+        }
+        if let Some(min_numeric_chars) = self.min_numeric_chars {
+            write!(f, " PASSWORD_MIN_NUMERIC_CHARS = {}", min_numeric_chars)?;
+        }
+        if let Some(min_special_chars) = self.min_special_chars {
+            write!(f, " PASSWORD_MIN_SPECIAL_CHARS = {}", min_special_chars)?;
+        }
+        if let Some(min_age_days) = self.min_age_days {
+            write!(f, " PASSWORD_MIN_AGE_DAYS = {}", min_age_days)?;
+        }
+        if let Some(max_age_days) = self.max_age_days {
+            write!(f, " PASSWORD_MAX_AGE_DAYS = {}", max_age_days)?;
+        }
+        if let Some(max_retries) = self.max_retries {
+            write!(f, " PASSWORD_MAX_RETRIES = {}", max_retries)?;
+        }
+        if let Some(lockout_time_mins) = self.lockout_time_mins {
+            write!(f, " PASSWORD_LOCKOUT_TIME_MINS = {}", lockout_time_mins)?;
+        }
+        if let Some(history) = self.history {
+            write!(f, " PASSWORD_HISTORY = {}", history)?;
+        }
+        if let Some(comment) = &self.comment {
+            write!(f, " COMMENT = '{}'", comment)?;
+        }
+
+        Ok(())
+    }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct PasswordUnSetOptions {
+    pub min_length: bool,
+    pub max_length: bool,
+    pub min_upper_case_chars: bool,
+    pub min_lower_case_chars: bool,
+    pub min_numeric_chars: bool,
+    pub min_special_chars: bool,
+    pub min_age_days: bool,
+    pub max_age_days: bool,
+    pub max_retries: bool,
+    pub lockout_time_mins: bool,
+    pub history: bool,
+    pub comment: bool,
+}
+
+impl Display for PasswordUnSetOptions {
+    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
+        if self.min_length {
+            write!(f, " PASSWORD_MIN_LENGTH")?;
+        }
+        if self.max_length {
+            write!(f, " PASSWORD_MAX_LENGTH")?;
+        }
+        if self.min_upper_case_chars {
+            write!(f, " PASSWORD_MIN_UPPER_CASE_CHARS")?;
+        }
+        if self.min_lower_case_chars {
+            write!(f, " PASSWORD_MIN_LOWER_CASE_CHARS")?;
+        }
+        if self.min_numeric_chars {
+            write!(f, " PASSWORD_MIN_NUMERIC_CHARS")?;
+        }
+        if self.min_special_chars {
+            write!(f, " PASSWORD_MIN_SPECIAL_CHARS")?;
+        }
+        if self.min_age_days {
+            write!(f, " PASSWORD_MIN_AGE_DAYS")?;
+        }
+        if self.max_age_days {
+            write!(f, " PASSWORD_MAX_AGE_DAYS")?;
+        }
+        if self.max_retries {
+            write!(f, " PASSWORD_MAX_RETRIES")?;
+        }
+        if self.lockout_time_mins {
+            write!(f, " PASSWORD_LOCKOUT_TIME_MINS")?;
+        }
+        if self.history {
+            write!(f, " PASSWORD_HISTORY")?;
+        }
+        if self.comment {
+            write!(f, " COMMENT")?;
+        }
+
+        Ok(())
+    }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct DropPasswordPolicyStmt {
+    pub if_exists: bool,
+    pub name: String,
+}
+
+impl Display for DropPasswordPolicyStmt {
+    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
+        write!(f, "DROP PASSWORD POLICY ")?;
+        if self.if_exists {
+            write!(f, "IF EXISTS ")?;
+        }
+        write!(f, "{}", self.name)?;
+
+        Ok(())
+    }
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct DescPasswordPolicyStmt {
+    pub name: String,
+}
+
+impl Display for DescPasswordPolicyStmt {
+    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
+        write!(f, "DESCRIBE PASSWORD POLICY {}", self.name)?;
+
+        Ok(())
+    }
+}
diff --git a/src/query/ast/src/ast/statements/statement.rs b/src/query/ast/src/ast/statements/statement.rs
index ac3e1b3cf5e14..97f12bcf83f07 100644
--- a/src/query/ast/src/ast/statements/statement.rs
+++ b/src/query/ast/src/ast/statements/statement.rs
@@ -253,6 +253,15 @@ pub enum Statement {
     DescNetworkPolicy(DescNetworkPolicyStmt),
     ShowNetworkPolicies,
 
+    // password policy
+    CreatePasswordPolicy(CreatePasswordPolicyStmt),
+    AlterPasswordPolicy(AlterPasswordPolicyStmt),
+    DropPasswordPolicy(DropPasswordPolicyStmt),
+    DescPasswordPolicy(DescPasswordPolicyStmt),
+    ShowPasswordPolicies {
+        show_options: Option<ShowOptions>,
+    },
+
     // tasks
     CreateTask(CreateTaskStmt),
     AlterTask(AlterTaskStmt),
@@ -590,6 +599,16 @@ impl Display for Statement {
             Statement::DropNetworkPolicy(stmt) => write!(f, "{stmt}")?,
             Statement::DescNetworkPolicy(stmt) => write!(f, "{stmt}")?,
             Statement::ShowNetworkPolicies => write!(f, "SHOW NETWORK POLICIES")?,
+            Statement::CreatePasswordPolicy(stmt) => write!(f, "{stmt}")?,
+            Statement::AlterPasswordPolicy(stmt) => write!(f, "{stmt}")?,
+            Statement::DropPasswordPolicy(stmt) => write!(f, "{stmt}")?,
+            Statement::DescPasswordPolicy(stmt) => write!(f, "{stmt}")?,
+            Statement::ShowPasswordPolicies { show_options } => {
+                write!(f, "SHOW PASSWORD POLICIES")?;
+                if let Some(show_options) = show_options {
+                    write!(f, " {show_options}")?;
+                }
+            }
             Statement::CreateTask(stmt) => write!(f, "{stmt}")?,
             Statement::AlterTask(stmt) => write!(f, "{stmt}")?,
             Statement::ExecuteTask(stmt) => write!(f, "{stmt}")?,
diff --git a/src/query/ast/src/ast/statements/user.rs b/src/query/ast/src/ast/statements/user.rs
index bacd63f294dbf..b4d75a3d7b6f4 100644
--- a/src/query/ast/src/ast/statements/user.rs
+++ b/src/query/ast/src/ast/statements/user.rs
@@ -168,6 +168,8 @@ pub enum UserOptionItem {
     DefaultRole(String),
     SetNetworkPolicy(String),
     UnsetNetworkPolicy,
+    SetPasswordPolicy(String),
+    UnsetPasswordPolicy,
 }
 
 impl UserOptionItem {
@@ -179,6 +181,8 @@ impl UserOptionItem {
             Self::DefaultRole(v) => option.set_default_role(Some(v.clone())),
             Self::SetNetworkPolicy(v) => option.set_network_policy(Some(v.clone())),
             Self::UnsetNetworkPolicy => option.set_network_policy(None),
+            Self::SetPasswordPolicy(v) => option.set_password_policy(Some(v.clone())),
+            Self::UnsetPasswordPolicy => option.set_password_policy(None),
         }
     }
 }
@@ -247,6 +251,8 @@ impl Display for UserOptionItem {
             UserOptionItem::DefaultRole(v) => write!(f, "DEFAULT_ROLE = '{}'", v),
             UserOptionItem::SetNetworkPolicy(v) => write!(f, "SET NETWORK POLICY = '{}'", v),
             UserOptionItem::UnsetNetworkPolicy => write!(f, "UNSET NETWORK POLICY"),
+            UserOptionItem::SetPasswordPolicy(v) => write!(f, "SET PASSWORD POLICY = '{}'", v),
+            UserOptionItem::UnsetPasswordPolicy => write!(f, "UNSET PASSWORD POLICY"),
         }
     }
 }
diff --git a/src/query/ast/src/parser/statement.rs b/src/query/ast/src/parser/statement.rs
index 745219128de9b..d5b440b00ca93 100644
--- a/src/query/ast/src/parser/statement.rs
+++ b/src/query/ast/src/parser/statement.rs
@@ -1521,10 +1521,10 @@ pub fn statement(i: Input) -> IResult<StatementWithFormat> {
 
     let create_network_policy = map(
         rule! {
-            CREATE ~ NETWORK ~ POLICY ~ ( IF ~ ^NOT ~ ^EXISTS )? ~ #ident
-             ~ ALLOWED_IP_LIST ~ Eq ~ "(" ~ ^#comma_separated_list0(literal_string) ~ ")"
-             ~ ( BLOCKED_IP_LIST ~ Eq ~ "(" ~ ^#comma_separated_list0(literal_string) ~ ")" ) ?
-             ~ ( COMMENT ~ Eq ~ #literal_string)?
+            CREATE ~ NETWORK ~ ^POLICY ~ ( IF ~ ^NOT ~ ^EXISTS )? ~ ^#ident
+             ~ ALLOWED_IP_LIST ~ ^Eq ~ ^"(" ~ ^#comma_separated_list0(literal_string) ~ ^")"
+             ~ ( BLOCKED_IP_LIST ~ ^Eq ~ ^"(" ~ ^#comma_separated_list0(literal_string) ~ ^")" ) ?
+             ~ ( COMMENT ~ ^Eq ~ ^#literal_string)?
         },
         |(
             _,
@@ -1558,10 +1558,10 @@ pub fn statement(i: Input) -> IResult<StatementWithFormat> {
     );
     let alter_network_policy = map(
         rule! {
-            ALTER ~ NETWORK ~ POLICY ~ ( IF ~ ^EXISTS )? ~ #ident ~ SET
-             ~ ( ALLOWED_IP_LIST ~ Eq ~ "(" ~ ^#comma_separated_list0(literal_string) ~ ")" ) ?
-             ~ ( BLOCKED_IP_LIST ~ Eq ~ "(" ~ ^#comma_separated_list0(literal_string) ~ ")" ) ?
-             ~ ( COMMENT ~ Eq ~ #literal_string)?
+            ALTER ~ NETWORK ~ ^POLICY ~ ( IF ~ ^EXISTS )? ~ ^#ident ~ SET
+             ~ ( ALLOWED_IP_LIST ~ ^Eq ~ ^"(" ~ ^#comma_separated_list0(literal_string) ~ ^")" ) ?
+             ~ ( BLOCKED_IP_LIST ~ ^Eq ~ ^"(" ~ ^#comma_separated_list0(literal_string) ~ ^")" ) ?
+             ~ ( COMMENT ~ ^Eq ~ ^#literal_string)?
         },
         |(
             _,
@@ -1595,7 +1595,7 @@ pub fn statement(i: Input) -> IResult<StatementWithFormat> {
     );
     let drop_network_policy = map(
         rule! {
-            DROP ~ NETWORK ~ POLICY ~ ( IF ~ ^EXISTS )? ~ #ident
+            DROP ~ NETWORK ~ ^POLICY ~ ( IF ~ ^EXISTS )? ~ ^#ident
         },
         |(_, _, _, opt_if_exists, name)| {
             let stmt = DropNetworkPolicyStmt {
@@ -1607,7 +1607,7 @@ pub fn statement(i: Input) -> IResult<StatementWithFormat> {
     );
     let describe_network_policy = map(
         rule! {
-            ( DESC | DESCRIBE ) ~ NETWORK ~ POLICY ~ #ident
+            ( DESC | DESCRIBE ) ~ NETWORK ~ ^POLICY ~ ^#ident
         },
         |(_, _, _, name)| {
             Statement::DescNetworkPolicy(DescNetworkPolicyStmt {
@@ -1617,7 +1617,64 @@ pub fn statement(i: Input) -> IResult<StatementWithFormat> {
     );
     let show_network_policies = value(
         Statement::ShowNetworkPolicies,
-        rule! { SHOW ~ NETWORK ~ POLICIES },
+        rule! { SHOW ~ NETWORK ~ ^POLICIES },
+    );
+
+    let create_password_policy = map(
+        rule! {
+            CREATE ~ PASSWORD ~ ^POLICY ~ ( IF ~ ^NOT ~ ^EXISTS )? ~ ^#ident
+             ~ #password_set_options
+        },
+        |(_, _, _, opt_if_not_exists, name, set_options)| {
+            let stmt = CreatePasswordPolicyStmt {
+                if_not_exists: opt_if_not_exists.is_some(),
+                name: name.to_string(),
+                set_options,
+            };
+            Statement::CreatePasswordPolicy(stmt)
+        },
+    );
+    let alter_password_policy = map(
+        rule! {
+            ALTER ~ PASSWORD ~ ^POLICY ~ ( IF ~ ^EXISTS )? ~ ^#ident
+             ~ #alter_password_action
+        },
+        |(_, _, _, opt_if_exists, name, action)| {
+            let stmt = AlterPasswordPolicyStmt {
+                if_exists: opt_if_exists.is_some(),
+                name: name.to_string(),
+                action,
+            };
+            Statement::AlterPasswordPolicy(stmt)
+        },
+    );
+    let drop_password_policy = map(
+        rule! {
+            DROP ~ PASSWORD ~ ^POLICY ~ ( IF ~ ^EXISTS )? ~ ^#ident
+        },
+        |(_, _, _, opt_if_exists, name)| {
+            let stmt = DropPasswordPolicyStmt {
+                if_exists: opt_if_exists.is_some(),
+                name: name.to_string(),
+            };
+            Statement::DropPasswordPolicy(stmt)
+        },
+    );
+    let describe_password_policy = map(
+        rule! {
+            ( DESC | DESCRIBE ) ~ PASSWORD ~ ^POLICY ~ ^#ident
+        },
+        |(_, _, _, name)| {
+            Statement::DescPasswordPolicy(DescPasswordPolicyStmt {
+                name: name.to_string(),
+            })
+        },
+    );
+    let show_password_policies = map(
+        rule! {
+            SHOW ~ PASSWORD ~ ^POLICIES ~ ^#show_options?
+        },
+        |(_, _, _, show_options)| Statement::ShowPasswordPolicies { show_options },
     );
 
     let create_pipe = map(
@@ -1707,13 +1764,18 @@ pub fn statement(i: Input) -> IResult<StatementWithFormat> {
             | #alter_database : "`ALTER DATABASE [IF EXISTS] <action>`"
             | #use_database : "`USE <database>`"
         ),
-        // network policy
+        // network policy / password policy
         rule!(
             #create_network_policy: "`CREATE NETWORK POLICY [IF NOT EXISTS] name ALLOWED_IP_LIST = ('ip1' [, 'ip2']) [BLOCKED_IP_LIST = ('ip1' [, 'ip2'])] [COMMENT = '<string_literal>']`"
             | #alter_network_policy: "`ALTER NETWORK POLICY [IF EXISTS] name SET [ALLOWED_IP_LIST = ('ip1' [, 'ip2'])] [BLOCKED_IP_LIST = ('ip1' [, 'ip2'])] [COMMENT = '<string_literal>']`"
             | #drop_network_policy: "`DROP NETWORK POLICY [IF EXISTS] name`"
             | #describe_network_policy: "`DESC NETWORK POLICY name`"
             | #show_network_policies: "`SHOW NETWORK POLICIES`"
+            | #create_password_policy: "`CREATE PASSWORD POLICY [IF NOT EXISTS] name [PASSWORD_MIN_LENGTH = <u64_literal>] ... [COMMENT = '<string_literal>']`"
+            | #alter_password_policy: "`ALTER PASSWORD POLICY [IF EXISTS] name SET [PASSWORD_MIN_LENGTH = <u64_literal>] ... [COMMENT = '<string_literal>']`"
+            | #drop_password_policy: "`DROP PASSWORD POLICY [IF EXISTS] name`"
+            | #describe_password_policy: "`DESC PASSWORD POLICY name`"
+            | #show_password_policies: "`SHOW PASSWORD POLICIES [<show_options>]`"
         ),
         rule!(
             #insert : "`INSERT INTO [TABLE] <table> [(<column>, ...)] (FORMAT <format> | VALUES <values> | <query>)`"
@@ -2907,14 +2969,16 @@ pub fn limit_where(i: Input) -> IResult<ShowLimit> {
     )(i)
 }
 
-pub fn show_limit(i: Input) -> IResult<ShowLimit> {
-    let limit_like = map(
+pub fn limit_like(i: Input) -> IResult<ShowLimit> {
+    map(
         rule! {
             LIKE ~ #literal_string
         },
         |(_, pattern)| ShowLimit::Like { pattern },
-    );
+    )(i)
+}
 
+pub fn show_limit(i: Input) -> IResult<ShowLimit> {
     rule!(
         #limit_like
         | #limit_where
@@ -3030,6 +3094,11 @@ pub fn catalog_type(i: Input) -> IResult<CatalogType> {
 }
 
 pub fn user_option(i: Input) -> IResult<UserOptionItem> {
+    let tenant_setting = value(UserOptionItem::TenantSetting(true), rule! { TENANTSETTING });
+    let no_tenant_setting = value(
+        UserOptionItem::TenantSetting(false),
+        rule! { NOTENANTSETTING },
+    );
     let default_role_option = map(
         rule! {
             "DEFAULT_ROLE" ~ ^"=" ~ ^#role_name
@@ -3038,26 +3107,38 @@ pub fn user_option(i: Input) -> IResult<UserOptionItem> {
     );
     let set_network_policy = map(
         rule! {
-            SET ~ ^NETWORK ~ ^POLICY ~ ^"=" ~ ^#literal_string
+            SET ~ NETWORK ~ ^POLICY ~ ^"=" ~ ^#literal_string
         },
         |(_, _, _, _, policy)| UserOptionItem::SetNetworkPolicy(policy),
     );
     let unset_network_policy = map(
         rule! {
-            UNSET ~ ^NETWORK ~ ^POLICY
+            UNSET ~ NETWORK ~ ^POLICY
         },
         |(_, _, _)| UserOptionItem::UnsetNetworkPolicy,
     );
-    alt((
-        value(UserOptionItem::TenantSetting(true), rule! { TENANTSETTING }),
-        value(
-            UserOptionItem::TenantSetting(false),
-            rule! { NOTENANTSETTING },
-        ),
-        default_role_option,
-        set_network_policy,
-        unset_network_policy,
-    ))(i)
+    let set_password_policy = map(
+        rule! {
+            SET ~ PASSWORD ~ ^POLICY ~ ^"=" ~ ^#literal_string
+        },
+        |(_, _, _, _, policy)| UserOptionItem::SetPasswordPolicy(policy),
+    );
+    let unset_password_policy = map(
+        rule! {
+            UNSET ~ PASSWORD ~ ^POLICY
+        },
+        |(_, _, _)| UserOptionItem::UnsetPasswordPolicy,
+    );
+
+    rule!(
+        #tenant_setting
+        | #no_tenant_setting
+        | #default_role_option
+        | #set_network_policy
+        | #unset_network_policy
+        | #set_password_policy
+        | #unset_password_policy
+    )(i)
 }
 
 pub fn user_identity(i: Input) -> IResult<UserIdentity> {
@@ -3213,3 +3294,119 @@ pub fn merge_update_expr(i: Input) -> IResult<MergeUpdateExpr> {
         |((table, name), _, expr)| MergeUpdateExpr { table, name, expr },
     )(i)
 }
+
+pub fn password_set_options(i: Input) -> IResult<PasswordSetOptions> {
+    map(
+        rule! {
+             ( PASSWORD_MIN_LENGTH ~ Eq ~ ^#literal_u64 ) ?
+             ~ ( PASSWORD_MAX_LENGTH ~ Eq ~ ^#literal_u64 ) ?
+             ~ ( PASSWORD_MIN_UPPER_CASE_CHARS ~ Eq ~ ^#literal_u64 ) ?
+             ~ ( PASSWORD_MIN_LOWER_CASE_CHARS ~ Eq ~ ^#literal_u64 ) ?
+             ~ ( PASSWORD_MIN_NUMERIC_CHARS ~ Eq ~ ^#literal_u64 ) ?
+             ~ ( PASSWORD_MIN_SPECIAL_CHARS ~ Eq ~ ^#literal_u64 ) ?
+             ~ ( PASSWORD_MIN_AGE_DAYS ~ Eq ~ ^#literal_u64 ) ?
+             ~ ( PASSWORD_MAX_AGE_DAYS ~ Eq ~ ^#literal_u64 ) ?
+             ~ ( PASSWORD_MAX_RETRIES ~ Eq ~ ^#literal_u64 ) ?
+             ~ ( PASSWORD_LOCKOUT_TIME_MINS ~ Eq ~ ^#literal_u64 ) ?
+             ~ ( PASSWORD_HISTORY ~ Eq ~ ^#literal_u64 ) ?
+             ~ ( COMMENT ~ Eq ~ ^#literal_string)?
+        },
+        |(
+            opt_min_length,
+            opt_max_length,
+            opt_min_upper_case_chars,
+            opt_min_lower_case_chars,
+            opt_min_numeric_chars,
+            opt_min_special_chars,
+            opt_min_age_days,
+            opt_max_age_days,
+            opt_max_retries,
+            opt_lockout_time_mins,
+            opt_history,
+            opt_comment,
+        )| {
+            PasswordSetOptions {
+                min_length: opt_min_length.map(|opt| opt.2),
+                max_length: opt_max_length.map(|opt| opt.2),
+                min_upper_case_chars: opt_min_upper_case_chars.map(|opt| opt.2),
+                min_lower_case_chars: opt_min_lower_case_chars.map(|opt| opt.2),
+                min_numeric_chars: opt_min_numeric_chars.map(|opt| opt.2),
+                min_special_chars: opt_min_special_chars.map(|opt| opt.2),
+                min_age_days: opt_min_age_days.map(|opt| opt.2),
+                max_age_days: opt_max_age_days.map(|opt| opt.2),
+                max_retries: opt_max_retries.map(|opt| opt.2),
+                lockout_time_mins: opt_lockout_time_mins.map(|opt| opt.2),
+                history: opt_history.map(|opt| opt.2),
+                comment: opt_comment.map(|opt| opt.2),
+            }
+        },
+    )(i)
+}
+
+pub fn password_unset_options(i: Input) -> IResult<PasswordUnSetOptions> {
+    map(
+        rule! {
+             PASSWORD_MIN_LENGTH ?
+             ~ PASSWORD_MAX_LENGTH ?
+             ~ PASSWORD_MIN_UPPER_CASE_CHARS ?
+             ~ PASSWORD_MIN_LOWER_CASE_CHARS ?
+             ~ PASSWORD_MIN_NUMERIC_CHARS ?
+             ~ PASSWORD_MIN_SPECIAL_CHARS ?
+             ~ PASSWORD_MIN_AGE_DAYS ?
+             ~ PASSWORD_MAX_AGE_DAYS ?
+             ~ PASSWORD_MAX_RETRIES ?
+             ~ PASSWORD_LOCKOUT_TIME_MINS ?
+             ~ PASSWORD_HISTORY ?
+             ~ COMMENT ?
+        },
+        |(
+            opt_min_length,
+            opt_max_length,
+            opt_min_upper_case_chars,
+            opt_min_lower_case_chars,
+            opt_min_numeric_chars,
+            opt_min_special_chars,
+            opt_min_age_days,
+            opt_max_age_days,
+            opt_max_retries,
+            opt_lockout_time_mins,
+            opt_history,
+            opt_comment,
+        )| {
+            PasswordUnSetOptions {
+                min_length: opt_min_length.is_some(),
+                max_length: opt_max_length.is_some(),
+                min_upper_case_chars: opt_min_upper_case_chars.is_some(),
+                min_lower_case_chars: opt_min_lower_case_chars.is_some(),
+                min_numeric_chars: opt_min_numeric_chars.is_some(),
+                min_special_chars: opt_min_special_chars.is_some(),
+                min_age_days: opt_min_age_days.is_some(),
+                max_age_days: opt_max_age_days.is_some(),
+                max_retries: opt_max_retries.is_some(),
+                lockout_time_mins: opt_lockout_time_mins.is_some(),
+                history: opt_history.is_some(),
+                comment: opt_comment.is_some(),
+            }
+        },
+    )(i)
+}
+
+pub fn alter_password_action(i: Input) -> IResult<AlterPasswordAction> {
+    let set_options = map(
+        rule! {
+           SET ~ #password_set_options
+        },
+        |(_, set_options)| AlterPasswordAction::SetOptions(set_options),
+    );
+    let unset_options = map(
+        rule! {
+           UNSET ~ #password_unset_options
+        },
+        |(_, unset_options)| AlterPasswordAction::UnSetOptions(unset_options),
+    );
+
+    rule!(
+        #set_options
+        | #unset_options
+    )(i)
+}
diff --git a/src/query/ast/src/parser/token.rs b/src/query/ast/src/parser/token.rs
index 867dc9f3a1207..c7f7f48934f9f 100644
--- a/src/query/ast/src/parser/token.rs
+++ b/src/query/ast/src/parser/token.rs
@@ -747,6 +747,30 @@ pub enum TokenKind {
     PARTITION,
     #[token("PARQUET", ignore(ascii_case))]
     PARQUET,
+    #[token("PASSWORD", ignore(ascii_case))]
+    PASSWORD,
+    #[token("PASSWORD_MIN_LENGTH", ignore(ascii_case))]
+    PASSWORD_MIN_LENGTH,
+    #[token("PASSWORD_MAX_LENGTH", ignore(ascii_case))]
+    PASSWORD_MAX_LENGTH,
+    #[token("PASSWORD_MIN_UPPER_CASE_CHARS", ignore(ascii_case))]
+    PASSWORD_MIN_UPPER_CASE_CHARS,
+    #[token("PASSWORD_MIN_LOWER_CASE_CHARS", ignore(ascii_case))]
+    PASSWORD_MIN_LOWER_CASE_CHARS,
+    #[token("PASSWORD_MIN_NUMERIC_CHARS", ignore(ascii_case))]
+    PASSWORD_MIN_NUMERIC_CHARS,
+    #[token("PASSWORD_MIN_SPECIAL_CHARS", ignore(ascii_case))]
+    PASSWORD_MIN_SPECIAL_CHARS,
+    #[token("PASSWORD_MIN_AGE_DAYS", ignore(ascii_case))]
+    PASSWORD_MIN_AGE_DAYS,
+    #[token("PASSWORD_MAX_AGE_DAYS", ignore(ascii_case))]
+    PASSWORD_MAX_AGE_DAYS,
+    #[token("PASSWORD_MAX_RETRIES", ignore(ascii_case))]
+    PASSWORD_MAX_RETRIES,
+    #[token("PASSWORD_LOCKOUT_TIME_MINS", ignore(ascii_case))]
+    PASSWORD_LOCKOUT_TIME_MINS,
+    #[token("PASSWORD_HISTORY", ignore(ascii_case))]
+    PASSWORD_HISTORY,
     #[token("PATTERN", ignore(ascii_case))]
     PATTERN,
     #[token("PIPELINE", ignore(ascii_case))]
diff --git a/src/query/ast/src/visitors/visitor.rs b/src/query/ast/src/visitors/visitor.rs
index c264243f81137..ec77a3328da96 100644
--- a/src/query/ast/src/visitors/visitor.rs
+++ b/src/query/ast/src/visitors/visitor.rs
@@ -672,6 +672,16 @@ pub trait Visitor<'ast>: Sized {
 
     fn visit_show_network_policies(&mut self) {}
 
+    fn visit_create_password_policy(&mut self, _stmt: &'ast CreatePasswordPolicyStmt) {}
+
+    fn visit_alter_password_policy(&mut self, _stmt: &'ast AlterPasswordPolicyStmt) {}
+
+    fn visit_drop_password_policy(&mut self, _stmt: &'ast DropPasswordPolicyStmt) {}
+
+    fn visit_desc_password_policy(&mut self, _stmt: &'ast DescPasswordPolicyStmt) {}
+
+    fn visit_show_password_policies(&mut self, _show_options: &'ast Option<ShowOptions>) {}
+
     fn visit_create_task(&mut self, _stmt: &'ast CreateTaskStmt) {}
 
     fn visit_drop_task(&mut self, _stmt: &'ast DropTaskStmt) {}
diff --git a/src/query/ast/src/visitors/visitor_mut.rs b/src/query/ast/src/visitors/visitor_mut.rs
index fa1cf82bc2370..114ab2962ebd3 100644
--- a/src/query/ast/src/visitors/visitor_mut.rs
+++ b/src/query/ast/src/visitors/visitor_mut.rs
@@ -686,6 +686,16 @@ pub trait VisitorMut: Sized {
 
     fn visit_show_network_policies(&mut self) {}
 
+    fn visit_create_password_policy(&mut self, _stmt: &mut CreatePasswordPolicyStmt) {}
+
+    fn visit_alter_password_policy(&mut self, _stmt: &mut AlterPasswordPolicyStmt) {}
+
+    fn visit_drop_password_policy(&mut self, _stmt: &mut DropPasswordPolicyStmt) {}
+
+    fn visit_desc_password_policy(&mut self, _stmt: &mut DescPasswordPolicyStmt) {}
+
+    fn visit_show_password_policies(&mut self, _show_options: &mut Option<ShowOptions>) {}
+
     fn visit_create_task(&mut self, _stmt: &mut CreateTaskStmt) {}
 
     fn visit_drop_task(&mut self, _stmt: &mut DropTaskStmt) {}
diff --git a/src/query/ast/src/visitors/walk.rs b/src/query/ast/src/visitors/walk.rs
index 13813a2d3ea7b..72c28634b2377 100644
--- a/src/query/ast/src/visitors/walk.rs
+++ b/src/query/ast/src/visitors/walk.rs
@@ -504,6 +504,13 @@ pub fn walk_statement<'a, V: Visitor<'a>>(visitor: &mut V, statement: &'a Statem
         Statement::DropNetworkPolicy(stmt) => visitor.visit_drop_network_policy(stmt),
         Statement::DescNetworkPolicy(stmt) => visitor.visit_desc_network_policy(stmt),
         Statement::ShowNetworkPolicies => visitor.visit_show_network_policies(),
+        Statement::CreatePasswordPolicy(stmt) => visitor.visit_create_password_policy(stmt),
+        Statement::AlterPasswordPolicy(stmt) => visitor.visit_alter_password_policy(stmt),
+        Statement::DropPasswordPolicy(stmt) => visitor.visit_drop_password_policy(stmt),
+        Statement::DescPasswordPolicy(stmt) => visitor.visit_desc_password_policy(stmt),
+        Statement::ShowPasswordPolicies { show_options } => {
+            visitor.visit_show_password_policies(show_options)
+        }
         Statement::CreateTask(stmt) => visitor.visit_create_task(stmt),
         Statement::ExecuteTask(stmt) => visitor.visit_execute_task(stmt),
         Statement::DropTask(stmt) => visitor.visit_drop_task(stmt),
diff --git a/src/query/ast/src/visitors/walk_mut.rs b/src/query/ast/src/visitors/walk_mut.rs
index 990da8deebb59..03ef1bd14b4a4 100644
--- a/src/query/ast/src/visitors/walk_mut.rs
+++ b/src/query/ast/src/visitors/walk_mut.rs
@@ -509,6 +509,13 @@ pub fn walk_statement_mut<V: VisitorMut>(visitor: &mut V, statement: &mut Statem
         Statement::DropNetworkPolicy(stmt) => visitor.visit_drop_network_policy(stmt),
         Statement::DescNetworkPolicy(stmt) => visitor.visit_desc_network_policy(stmt),
         Statement::ShowNetworkPolicies => visitor.visit_show_network_policies(),
+        Statement::CreatePasswordPolicy(stmt) => visitor.visit_create_password_policy(stmt),
+        Statement::AlterPasswordPolicy(stmt) => visitor.visit_alter_password_policy(stmt),
+        Statement::DropPasswordPolicy(stmt) => visitor.visit_drop_password_policy(stmt),
+        Statement::DescPasswordPolicy(stmt) => visitor.visit_desc_password_policy(stmt),
+        Statement::ShowPasswordPolicies { show_options } => {
+            visitor.visit_show_password_policies(show_options)
+        }
 
         Statement::CreateTask(stmt) => visitor.visit_create_task(stmt),
         Statement::ExecuteTask(stmt) => visitor.visit_execute_task(stmt),
diff --git a/src/query/ast/tests/it/testdata/statement-error.txt b/src/query/ast/tests/it/testdata/statement-error.txt
index 5236c275c3c9c..4b94a3eda84b7 100644
--- a/src/query/ast/tests/it/testdata/statement-error.txt
+++ b/src/query/ast/tests/it/testdata/statement-error.txt
@@ -131,7 +131,7 @@ error:
   --> SQL:1:6
   |
 1 | drop a
-  |      ^ unexpected `a`, expecting `TASK`, `TABLE`, `MASKING`, `CATALOG`, `DATABASE`, `AGGREGATING`, `SCHEMA`, `NETWORK`, `VIEW`, `STREAM`, `VIRTUAL`, `USER`, `ROLE`, `FUNCTION`, `STAGE`, `FILE`, `SHARE`, `PIPE`, or `CONNECTION`
+  |      ^ unexpected `a`, expecting `TASK`, `TABLE`, `MASKING`, `CATALOG`, `DATABASE`, `PASSWORD`, `AGGREGATING`, `SCHEMA`, `NETWORK`, `VIEW`, `STREAM`, `VIRTUAL`, `USER`, `ROLE`, `FUNCTION`, `STAGE`, `FILE`, `SHARE`, `PIPE`, or `CONNECTION`
 
 
 ---------- Input ----------
@@ -197,7 +197,7 @@ error:
   --> SQL:1:6
   |
 1 | drop usar if exists 'test-j';
-  |      ^^^^ unexpected `usar`, expecting `USER`, `SHARE`, `STREAM`, `STAGE`, `AGGREGATING`, `ROLE`, `TABLE`, `SCHEMA`, `NETWORK`, `VIRTUAL`, `CATALOG`, `DATABASE`, `FUNCTION`, `TASK`, `MASKING`, `VIEW`, `FILE`, `PIPE`, or `CONNECTION`
+  |      ^^^^ unexpected `usar`, expecting `USER`, `SHARE`, `STREAM`, `STAGE`, `PASSWORD`, `AGGREGATING`, `ROLE`, `TABLE`, `SCHEMA`, `NETWORK`, `VIRTUAL`, `CATALOG`, `DATABASE`, `FUNCTION`, `TASK`, `MASKING`, `VIEW`, `FILE`, `PIPE`, or `CONNECTION`
 
 
 ---------- Input ----------
@@ -237,7 +237,7 @@ error:
   --> SQL:1:6
   |
 1 | SHOW GRANT FOR ROLE 'role1';
-  |      ^^^^^ unexpected `GRANT`, expecting `GRANTS`, `CREATE`, `NETWORK`, `VIRTUAL`, `STREAMS`, `CATALOGS`, `FUNCTIONS`, `DATABASES`, `CONNECTIONS`, `TABLE_FUNCTIONS`, `DROP`, `TABLE`, `ROLES`, `SHARE`, `TASKS`, `INDEXES`, `COLUMNS`, `PROCESSLIST`, `STAGES`, `TABLES`, `SHARES`, `ENGINES`, `METRICS`, `SETTINGS`, `LOCKS`, `SCHEMAS`, `FIELDS`, `USERS`, `FILE`, or `FULL`
+  |      ^^^^^ unexpected `GRANT`, expecting `GRANTS`, `CREATE`, `NETWORK`, `VIRTUAL`, `STREAMS`, `CATALOGS`, `FUNCTIONS`, `DATABASES`, `CONNECTIONS`, `TABLE_FUNCTIONS`, `DROP`, `TABLE`, `ROLES`, `SHARE`, `TASKS`, `INDEXES`, `COLUMNS`, `PASSWORD`, `PROCESSLIST`, `STAGES`, `TABLES`, `SHARES`, `ENGINES`, `METRICS`, `SETTINGS`, `LOCKS`, `SCHEMAS`, `FIELDS`, `USERS`, `FILE`, or `FULL`
 
 
 ---------- Input ----------
diff --git a/src/query/management/src/lib.rs b/src/query/management/src/lib.rs
index 35c959bb8b9a5..c70a693fe09bb 100644
--- a/src/query/management/src/lib.rs
+++ b/src/query/management/src/lib.rs
@@ -18,6 +18,7 @@ mod cluster;
 mod connection;
 mod file_format;
 mod network_policy;
+mod password_policy;
 mod quota;
 mod role;
 mod serde;
@@ -34,6 +35,8 @@ pub use file_format::FileFormatApi;
 pub use file_format::FileFormatMgr;
 pub use network_policy::NetworkPolicyApi;
 pub use network_policy::NetworkPolicyMgr;
+pub use password_policy::PasswordPolicyApi;
+pub use password_policy::PasswordPolicyMgr;
 pub use quota::QuotaApi;
 pub use quota::QuotaMgr;
 pub use role::RoleApi;
diff --git a/src/query/management/src/password_policy/mod.rs b/src/query/management/src/password_policy/mod.rs
new file mode 100644
index 0000000000000..33bab1ac7e0be
--- /dev/null
+++ b/src/query/management/src/password_policy/mod.rs
@@ -0,0 +1,19 @@
+// Copyright 2021 Datafuse Labs
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+mod password_policy_api;
+mod password_policy_mgr;
+
+pub use password_policy_api::PasswordPolicyApi;
+pub use password_policy_mgr::PasswordPolicyMgr;
diff --git a/src/query/management/src/password_policy/password_policy_api.rs b/src/query/management/src/password_policy/password_policy_api.rs
new file mode 100644
index 0000000000000..6217cf8eb9718
--- /dev/null
+++ b/src/query/management/src/password_policy/password_policy_api.rs
@@ -0,0 +1,35 @@
+// Copyright 2021 Datafuse Labs
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use databend_common_exception::Result;
+use databend_common_meta_app::principal::PasswordPolicy;
+use databend_common_meta_types::MatchSeq;
+use databend_common_meta_types::SeqV;
+
+#[async_trait::async_trait]
+pub trait PasswordPolicyApi: Sync + Send {
+    async fn add_password_policy(&self, password_policy: PasswordPolicy) -> Result<u64>;
+
+    async fn update_password_policy(
+        &self,
+        password_policy: PasswordPolicy,
+        seq: MatchSeq,
+    ) -> Result<u64>;
+
+    async fn drop_password_policy(&self, name: &str, seq: MatchSeq) -> Result<()>;
+
+    async fn get_password_policy(&self, name: &str, seq: MatchSeq) -> Result<SeqV<PasswordPolicy>>;
+
+    async fn get_password_policies(&self) -> Result<Vec<PasswordPolicy>>;
+}
diff --git a/src/query/management/src/password_policy/password_policy_mgr.rs b/src/query/management/src/password_policy/password_policy_mgr.rs
new file mode 100644
index 0000000000000..d7ed580595d3e
--- /dev/null
+++ b/src/query/management/src/password_policy/password_policy_mgr.rs
@@ -0,0 +1,175 @@
+// Copyright 2021 Datafuse Labs
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::sync::Arc;
+
+use databend_common_base::base::escape_for_key;
+use databend_common_exception::ErrorCode;
+use databend_common_exception::Result;
+use databend_common_meta_app::principal::PasswordPolicy;
+use databend_common_meta_kvapi::kvapi;
+use databend_common_meta_kvapi::kvapi::UpsertKVReq;
+use databend_common_meta_types::MatchSeq;
+use databend_common_meta_types::MatchSeqExt;
+use databend_common_meta_types::MetaError;
+use databend_common_meta_types::Operation;
+use databend_common_meta_types::SeqV;
+
+use crate::password_policy::password_policy_api::PasswordPolicyApi;
+use crate::serde::deserialize_struct;
+use crate::serde::serialize_struct;
+
+static PASSWORD_POLICY_API_KEY_PREFIX: &str = "__fd_password_policies";
+
+pub struct PasswordPolicyMgr {
+    kv_api: Arc<dyn kvapi::KVApi<Error = MetaError>>,
+    password_policy_prefix: String,
+}
+
+impl PasswordPolicyMgr {
+    pub fn create(
+        kv_api: Arc<dyn kvapi::KVApi<Error = MetaError>>,
+        tenant: &str,
+    ) -> Result<Self, ErrorCode> {
+        if tenant.is_empty() {
+            return Err(ErrorCode::TenantIsEmpty(
+                "Tenant can not empty (while create password policy)",
+            ));
+        }
+
+        Ok(PasswordPolicyMgr {
+            kv_api,
+            password_policy_prefix: format!("{}/{}", PASSWORD_POLICY_API_KEY_PREFIX, tenant),
+        })
+    }
+
+    fn make_password_policy_key(&self, name: &str) -> Result<String> {
+        Ok(format!(
+            "{}/{}",
+            self.password_policy_prefix,
+            escape_for_key(name)?
+        ))
+    }
+}
+
+#[async_trait::async_trait]
+impl PasswordPolicyApi for PasswordPolicyMgr {
+    #[async_backtrace::framed]
+    #[minitrace::trace]
+    async fn add_password_policy(&self, password_policy: PasswordPolicy) -> Result<u64> {
+        let match_seq = MatchSeq::Exact(0);
+        let key = self.make_password_policy_key(password_policy.name.as_str())?;
+        let value = Operation::Update(serialize_struct(
+            &password_policy,
+            ErrorCode::IllegalPasswordPolicy,
+            || "",
+        )?);
+
+        let kv_api = self.kv_api.clone();
+        let upsert_kv = kv_api.upsert_kv(UpsertKVReq::new(&key, match_seq, value, None));
+
+        let res_seq = upsert_kv.await?.added_seq_or_else(|v| {
+            ErrorCode::PasswordPolicyAlreadyExists(format!(
+                "PasswordPolicy already exists, seq [{}]",
+                v.seq
+            ))
+        })?;
+
+        Ok(res_seq)
+    }
+
+    #[async_backtrace::framed]
+    #[minitrace::trace]
+    async fn update_password_policy(
+        &self,
+        password_policy: PasswordPolicy,
+        match_seq: MatchSeq,
+    ) -> Result<u64> {
+        let key = self.make_password_policy_key(password_policy.name.as_str())?;
+        let value = Operation::Update(serialize_struct(
+            &password_policy,
+            ErrorCode::IllegalPasswordPolicy,
+            || "",
+        )?);
+
+        let kv_api = self.kv_api.clone();
+        let upsert_kv = kv_api
+            .upsert_kv(UpsertKVReq::new(&key, match_seq, value, None))
+            .await?;
+
+        match upsert_kv.result {
+            Some(SeqV { seq: s, .. }) => Ok(s),
+            None => Err(ErrorCode::UnknownPasswordPolicy(format!(
+                "Unknown PasswordPolicy, or seq not match {}",
+                password_policy.name.clone()
+            ))),
+        }
+    }
+
+    #[async_backtrace::framed]
+    #[minitrace::trace]
+    async fn drop_password_policy(&self, name: &str, seq: MatchSeq) -> Result<()> {
+        let key = self.make_password_policy_key(name)?;
+        let kv_api = self.kv_api.clone();
+        let res = kv_api
+            .upsert_kv(UpsertKVReq::new(&key, seq, Operation::Delete, None))
+            .await?;
+        if res.prev.is_some() && res.result.is_none() {
+            Ok(())
+        } else {
+            Err(ErrorCode::UnknownPasswordPolicy(format!(
+                "Unknown PasswordPolicy {}",
+                name
+            )))
+        }
+    }
+
+    #[async_backtrace::framed]
+    #[minitrace::trace]
+    async fn get_password_policy(&self, name: &str, seq: MatchSeq) -> Result<SeqV<PasswordPolicy>> {
+        let key = self.make_password_policy_key(name)?;
+        let res = self.kv_api.get_kv(&key).await?;
+        let seq_value = res.ok_or_else(|| {
+            ErrorCode::UnknownPasswordPolicy(format!("Unknown PasswordPolicy {}", name))
+        })?;
+
+        match seq.match_seq(&seq_value) {
+            Ok(_) => Ok(SeqV::new(
+                seq_value.seq,
+                deserialize_struct(&seq_value.data, ErrorCode::IllegalPasswordPolicy, || "")?,
+            )),
+            Err(_) => Err(ErrorCode::UnknownPasswordPolicy(format!(
+                "Unknown PasswordPolicy {}",
+                name
+            ))),
+        }
+    }
+
+    #[async_backtrace::framed]
+    #[minitrace::trace]
+    async fn get_password_policies(&self) -> Result<Vec<PasswordPolicy>> {
+        let values = self
+            .kv_api
+            .prefix_list_kv(&self.password_policy_prefix)
+            .await?;
+
+        let mut password_policies = Vec::with_capacity(values.len());
+        for (_, value) in values {
+            let password_policy =
+                deserialize_struct(&value.data, ErrorCode::IllegalPasswordPolicy, || "")?;
+            password_policies.push(password_policy);
+        }
+        Ok(password_policies)
+    }
+}
diff --git a/src/query/service/src/databases/system/system_database.rs b/src/query/service/src/databases/system/system_database.rs
index 9fd11b1e7c456..328b118121b1d 100644
--- a/src/query/service/src/databases/system/system_database.rs
+++ b/src/query/service/src/databases/system/system_database.rs
@@ -41,6 +41,7 @@ use databend_common_storages_system::MallocStatsTable;
 use databend_common_storages_system::MallocStatsTotalsTable;
 use databend_common_storages_system::MetricsTable;
 use databend_common_storages_system::OneTable;
+use databend_common_storages_system::PasswordPoliciesTable;
 use databend_common_storages_system::ProcessesTable;
 use databend_common_storages_system::ProcessorProfileTable;
 use databend_common_storages_system::QueryCacheTable;
@@ -127,6 +128,7 @@ impl SystemDatabase {
             ProcessorProfileTable::create(sys_db_meta.next_table_id()),
             LocksTable::create(sys_db_meta.next_table_id()),
             VirtualColumnsTable::create(sys_db_meta.next_table_id()),
+            PasswordPoliciesTable::create(sys_db_meta.next_table_id()),
         ];
 
         let disable_tables = Self::disable_system_tables();
diff --git a/src/query/service/src/interpreters/access/management_mode_access.rs b/src/query/service/src/interpreters/access/management_mode_access.rs
index 0bab8c57edec5..f871d4c579bc9 100644
--- a/src/query/service/src/interpreters/access/management_mode_access.rs
+++ b/src/query/service/src/interpreters/access/management_mode_access.rs
@@ -104,6 +104,10 @@ impl AccessChecker for ManagementModeAccess {
                 | Plan::CreateNetworkPolicy(_)
                 | Plan::AlterNetworkPolicy(_)
                 | Plan::DropNetworkPolicy(_)
+                // Password policy.
+                | Plan::CreatePasswordPolicy(_)
+                | Plan::AlterPasswordPolicy(_)
+                | Plan::DropPasswordPolicy(_)
 
                 // UDF
                 | Plan::CreateUDF(_)
diff --git a/src/query/service/src/interpreters/access/privilege_access.rs b/src/query/service/src/interpreters/access/privilege_access.rs
index ff547435adc71..e1357c38bce71 100644
--- a/src/query/service/src/interpreters/access/privilege_access.rs
+++ b/src/query/service/src/interpreters/access/privilege_access.rs
@@ -932,6 +932,10 @@ impl AccessChecker for PrivilegeAccess {
             | Plan::DropNetworkPolicy(_)
             | Plan::DescNetworkPolicy(_)
             | Plan::ShowNetworkPolicies(_)
+            | Plan::CreatePasswordPolicy(_)
+            | Plan::AlterPasswordPolicy(_)
+            | Plan::DropPasswordPolicy(_)
+            | Plan::DescPasswordPolicy(_)
             | Plan::CreateConnection(_)
             | Plan::ShowConnections(_)
             | Plan::DescConnection(_)
diff --git a/src/query/service/src/interpreters/interpreter_factory.rs b/src/query/service/src/interpreters/interpreter_factory.rs
index 4e394dc38924c..f373276f51d65 100644
--- a/src/query/service/src/interpreters/interpreter_factory.rs
+++ b/src/query/service/src/interpreters/interpreter_factory.rs
@@ -471,6 +471,20 @@ impl InterpreterFactory {
             Plan::ShowNetworkPolicies(_) => {
                 Ok(Arc::new(ShowNetworkPoliciesInterpreter::try_create(ctx)?))
             }
+            Plan::CreatePasswordPolicy(p) => Ok(Arc::new(
+                CreatePasswordPolicyInterpreter::try_create(ctx, *p.clone())?,
+            )),
+            Plan::AlterPasswordPolicy(p) => Ok(Arc::new(
+                AlterPasswordPolicyInterpreter::try_create(ctx, *p.clone())?,
+            )),
+            Plan::DropPasswordPolicy(p) => Ok(Arc::new(DropPasswordPolicyInterpreter::try_create(
+                ctx,
+                *p.clone(),
+            )?)),
+            Plan::DescPasswordPolicy(p) => Ok(Arc::new(DescPasswordPolicyInterpreter::try_create(
+                ctx,
+                *p.clone(),
+            )?)),
 
             Plan::CreateTask(p) => Ok(Arc::new(CreateTaskInterpreter::try_create(
                 ctx,
diff --git a/src/query/service/src/interpreters/interpreter_password_policy_alter.rs b/src/query/service/src/interpreters/interpreter_password_policy_alter.rs
new file mode 100644
index 0000000000000..54037769241fd
--- /dev/null
+++ b/src/query/service/src/interpreters/interpreter_password_policy_alter.rs
@@ -0,0 +1,172 @@
+// Copyright 2021 Datafuse Labs
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::sync::Arc;
+
+use databend_common_ast::ast::AlterPasswordAction;
+use databend_common_exception::Result;
+use databend_common_sql::plans::AlterPasswordPolicyPlan;
+use databend_common_users::UserApiProvider;
+use databend_common_users::DEFAULT_PASSWORD_HISTORY;
+use databend_common_users::DEFAULT_PASSWORD_LOCKOUT_TIME_MINS;
+use databend_common_users::DEFAULT_PASSWORD_MAX_AGE_DAYS;
+use databend_common_users::DEFAULT_PASSWORD_MAX_LENGTH;
+use databend_common_users::DEFAULT_PASSWORD_MAX_RETRIES;
+use databend_common_users::DEFAULT_PASSWORD_MIN_AGE_DAYS;
+use databend_common_users::DEFAULT_PASSWORD_MIN_CHARS;
+use databend_common_users::DEFAULT_PASSWORD_MIN_LENGTH;
+use databend_common_users::DEFAULT_PASSWORD_MIN_SPECIAL_CHARS;
+use log::debug;
+
+use crate::interpreters::Interpreter;
+use crate::pipelines::PipelineBuildResult;
+use crate::sessions::QueryContext;
+use crate::sessions::TableContext;
+
+#[derive(Debug)]
+pub struct AlterPasswordPolicyInterpreter {
+    ctx: Arc<QueryContext>,
+    plan: AlterPasswordPolicyPlan,
+}
+
+impl AlterPasswordPolicyInterpreter {
+    pub fn try_create(ctx: Arc<QueryContext>, plan: AlterPasswordPolicyPlan) -> Result<Self> {
+        Ok(AlterPasswordPolicyInterpreter { ctx, plan })
+    }
+}
+
+#[async_trait::async_trait]
+impl Interpreter for AlterPasswordPolicyInterpreter {
+    fn name(&self) -> &str {
+        "AlterPasswordPolicyInterpreter"
+    }
+
+    #[minitrace::trace]
+    #[async_backtrace::framed]
+    async fn execute2(&self) -> Result<PipelineBuildResult> {
+        debug!("ctx.id" = self.ctx.get_id().as_str(); "alter_password_policy_execute");
+
+        let plan = self.plan.clone();
+        let tenant = self.ctx.get_tenant();
+        let user_mgr = UserApiProvider::instance();
+
+        match plan.action {
+            AlterPasswordAction::SetOptions(set_options) => {
+                user_mgr
+                    .update_password_policy(
+                        &tenant,
+                        &plan.name,
+                        set_options.min_length,
+                        set_options.max_length,
+                        set_options.min_upper_case_chars,
+                        set_options.min_lower_case_chars,
+                        set_options.min_numeric_chars,
+                        set_options.min_special_chars,
+                        set_options.min_age_days,
+                        set_options.max_age_days,
+                        set_options.max_retries,
+                        set_options.lockout_time_mins,
+                        set_options.history,
+                        set_options.comment.clone(),
+                        plan.if_exists,
+                    )
+                    .await?;
+            }
+            AlterPasswordAction::UnSetOptions(unset_options) => {
+                // convert unset options to default values
+                let min_length = if unset_options.min_length {
+                    Some(DEFAULT_PASSWORD_MIN_LENGTH)
+                } else {
+                    None
+                };
+                let max_length = if unset_options.max_length {
+                    Some(DEFAULT_PASSWORD_MAX_LENGTH)
+                } else {
+                    None
+                };
+                let min_upper_case_chars = if unset_options.min_upper_case_chars {
+                    Some(DEFAULT_PASSWORD_MIN_CHARS)
+                } else {
+                    None
+                };
+                let min_lower_case_chars = if unset_options.min_lower_case_chars {
+                    Some(DEFAULT_PASSWORD_MIN_CHARS)
+                } else {
+                    None
+                };
+                let min_numeric_chars = if unset_options.min_numeric_chars {
+                    Some(DEFAULT_PASSWORD_MIN_CHARS)
+                } else {
+                    None
+                };
+                let min_special_chars = if unset_options.min_special_chars {
+                    Some(DEFAULT_PASSWORD_MIN_SPECIAL_CHARS)
+                } else {
+                    None
+                };
+                let min_age_days = if unset_options.min_age_days {
+                    Some(DEFAULT_PASSWORD_MIN_AGE_DAYS)
+                } else {
+                    None
+                };
+                let max_age_days = if unset_options.max_age_days {
+                    Some(DEFAULT_PASSWORD_MAX_AGE_DAYS)
+                } else {
+                    None
+                };
+                let max_retries = if unset_options.max_retries {
+                    Some(DEFAULT_PASSWORD_MAX_RETRIES)
+                } else {
+                    None
+                };
+                let lockout_time_mins = if unset_options.lockout_time_mins {
+                    Some(DEFAULT_PASSWORD_LOCKOUT_TIME_MINS)
+                } else {
+                    None
+                };
+                let history = if unset_options.history {
+                    Some(DEFAULT_PASSWORD_HISTORY)
+                } else {
+                    None
+                };
+                let comment = if unset_options.comment {
+                    Some("".to_string())
+                } else {
+                    None
+                };
+                user_mgr
+                    .update_password_policy(
+                        &tenant,
+                        &plan.name,
+                        min_length,
+                        max_length,
+                        min_upper_case_chars,
+                        min_lower_case_chars,
+                        min_numeric_chars,
+                        min_special_chars,
+                        min_age_days,
+                        max_age_days,
+                        max_retries,
+                        lockout_time_mins,
+                        history,
+                        comment,
+                        plan.if_exists,
+                    )
+                    .await?;
+            }
+        }
+
+        Ok(PipelineBuildResult::create())
+    }
+}
diff --git a/src/query/service/src/interpreters/interpreter_password_policy_create.rs b/src/query/service/src/interpreters/interpreter_password_policy_create.rs
new file mode 100644
index 0000000000000..8cee486852824
--- /dev/null
+++ b/src/query/service/src/interpreters/interpreter_password_policy_create.rs
@@ -0,0 +1,132 @@
+// Copyright 2021 Datafuse Labs
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::sync::Arc;
+
+use chrono::Utc;
+use databend_common_exception::Result;
+use databend_common_meta_app::principal::PasswordPolicy;
+use databend_common_sql::plans::CreatePasswordPolicyPlan;
+use databend_common_users::UserApiProvider;
+use databend_common_users::DEFAULT_PASSWORD_HISTORY;
+use databend_common_users::DEFAULT_PASSWORD_LOCKOUT_TIME_MINS;
+use databend_common_users::DEFAULT_PASSWORD_MAX_AGE_DAYS;
+use databend_common_users::DEFAULT_PASSWORD_MAX_LENGTH;
+use databend_common_users::DEFAULT_PASSWORD_MAX_RETRIES;
+use databend_common_users::DEFAULT_PASSWORD_MIN_AGE_DAYS;
+use databend_common_users::DEFAULT_PASSWORD_MIN_CHARS;
+use databend_common_users::DEFAULT_PASSWORD_MIN_LENGTH;
+use databend_common_users::DEFAULT_PASSWORD_MIN_SPECIAL_CHARS;
+use log::debug;
+
+use crate::interpreters::Interpreter;
+use crate::pipelines::PipelineBuildResult;
+use crate::sessions::QueryContext;
+use crate::sessions::TableContext;
+
+#[derive(Debug)]
+pub struct CreatePasswordPolicyInterpreter {
+    ctx: Arc<QueryContext>,
+    plan: CreatePasswordPolicyPlan,
+}
+
+impl CreatePasswordPolicyInterpreter {
+    pub fn try_create(ctx: Arc<QueryContext>, plan: CreatePasswordPolicyPlan) -> Result<Self> {
+        Ok(CreatePasswordPolicyInterpreter { ctx, plan })
+    }
+}
+
+#[async_trait::async_trait]
+impl Interpreter for CreatePasswordPolicyInterpreter {
+    fn name(&self) -> &str {
+        "CreatePasswordPolicyInterpreter"
+    }
+
+    #[minitrace::trace]
+    #[async_backtrace::framed]
+    async fn execute2(&self) -> Result<PipelineBuildResult> {
+        debug!("ctx.id" = self.ctx.get_id().as_str(); "create_password_policy_execute");
+
+        let plan = self.plan.clone();
+        let tenant = self.ctx.get_tenant();
+        let user_mgr = UserApiProvider::instance();
+
+        let min_length = plan
+            .set_options
+            .min_length
+            .unwrap_or(DEFAULT_PASSWORD_MIN_LENGTH);
+        let max_length = plan
+            .set_options
+            .max_length
+            .unwrap_or(DEFAULT_PASSWORD_MAX_LENGTH);
+        let min_upper_case_chars = plan
+            .set_options
+            .min_upper_case_chars
+            .unwrap_or(DEFAULT_PASSWORD_MIN_CHARS);
+        let min_lower_case_chars = plan
+            .set_options
+            .min_lower_case_chars
+            .unwrap_or(DEFAULT_PASSWORD_MIN_CHARS);
+        let min_numeric_chars = plan
+            .set_options
+            .min_numeric_chars
+            .unwrap_or(DEFAULT_PASSWORD_MIN_CHARS);
+        let min_special_chars = plan
+            .set_options
+            .min_special_chars
+            .unwrap_or(DEFAULT_PASSWORD_MIN_SPECIAL_CHARS);
+        let min_age_days = plan
+            .set_options
+            .min_age_days
+            .unwrap_or(DEFAULT_PASSWORD_MIN_AGE_DAYS);
+        let max_age_days = plan
+            .set_options
+            .max_age_days
+            .unwrap_or(DEFAULT_PASSWORD_MAX_AGE_DAYS);
+        let max_retries = plan
+            .set_options
+            .max_retries
+            .unwrap_or(DEFAULT_PASSWORD_MAX_RETRIES);
+        let lockout_time_mins = plan
+            .set_options
+            .lockout_time_mins
+            .unwrap_or(DEFAULT_PASSWORD_LOCKOUT_TIME_MINS);
+        let history = plan.set_options.history.unwrap_or(DEFAULT_PASSWORD_HISTORY);
+
+        let comment = plan.set_options.comment.clone().unwrap_or_default();
+
+        let password_policy = PasswordPolicy {
+            name: plan.name,
+            min_length,
+            max_length,
+            min_upper_case_chars,
+            min_lower_case_chars,
+            min_numeric_chars,
+            min_special_chars,
+            min_age_days,
+            max_age_days,
+            max_retries,
+            lockout_time_mins,
+            history,
+            comment,
+            create_on: Utc::now(),
+            update_on: None,
+        };
+        user_mgr
+            .add_password_policy(&tenant, password_policy, plan.if_not_exists)
+            .await?;
+
+        Ok(PipelineBuildResult::create())
+    }
+}
diff --git a/src/query/service/src/interpreters/interpreter_password_policy_desc.rs b/src/query/service/src/interpreters/interpreter_password_policy_desc.rs
new file mode 100644
index 0000000000000..8aafd73e6aeb7
--- /dev/null
+++ b/src/query/service/src/interpreters/interpreter_password_policy_desc.rs
@@ -0,0 +1,149 @@
+// Copyright 2021 Datafuse Labs
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::sync::Arc;
+
+use databend_common_exception::Result;
+use databend_common_expression::types::StringType;
+use databend_common_expression::types::UInt64Type;
+use databend_common_expression::DataBlock;
+use databend_common_expression::FromData;
+use databend_common_sql::plans::DescPasswordPolicyPlan;
+use databend_common_users::UserApiProvider;
+use databend_common_users::DEFAULT_PASSWORD_HISTORY;
+use databend_common_users::DEFAULT_PASSWORD_LOCKOUT_TIME_MINS;
+use databend_common_users::DEFAULT_PASSWORD_MAX_AGE_DAYS;
+use databend_common_users::DEFAULT_PASSWORD_MAX_LENGTH;
+use databend_common_users::DEFAULT_PASSWORD_MAX_RETRIES;
+use databend_common_users::DEFAULT_PASSWORD_MIN_AGE_DAYS;
+use databend_common_users::DEFAULT_PASSWORD_MIN_CHARS;
+use databend_common_users::DEFAULT_PASSWORD_MIN_LENGTH;
+use databend_common_users::DEFAULT_PASSWORD_MIN_SPECIAL_CHARS;
+
+use crate::interpreters::Interpreter;
+use crate::pipelines::PipelineBuildResult;
+use crate::sessions::QueryContext;
+use crate::sessions::TableContext;
+
+#[derive(Debug)]
+pub struct DescPasswordPolicyInterpreter {
+    ctx: Arc<QueryContext>,
+    plan: DescPasswordPolicyPlan,
+}
+
+impl DescPasswordPolicyInterpreter {
+    pub fn try_create(ctx: Arc<QueryContext>, plan: DescPasswordPolicyPlan) -> Result<Self> {
+        Ok(DescPasswordPolicyInterpreter { ctx, plan })
+    }
+}
+
+#[async_trait::async_trait]
+impl Interpreter for DescPasswordPolicyInterpreter {
+    fn name(&self) -> &str {
+        "DescPasswordPolicyInterpreter"
+    }
+
+    #[async_backtrace::framed]
+    async fn execute2(&self) -> Result<PipelineBuildResult> {
+        let tenant = self.ctx.get_tenant();
+        let user_mgr = UserApiProvider::instance();
+
+        let password_policy = user_mgr
+            .get_password_policy(&tenant, self.plan.name.as_str())
+            .await?;
+
+        let properties = vec![
+            "NAME".as_bytes().to_vec(),
+            "COMMENT".as_bytes().to_vec(),
+            "PASSWORD_MIN_LENGTH".as_bytes().to_vec(),
+            "PASSWORD_MAX_LENGTH".as_bytes().to_vec(),
+            "PASSWORD_MIN_UPPER_CASE_CHARS".as_bytes().to_vec(),
+            "PASSWORD_MIN_LOWER_CASE_CHARS".as_bytes().to_vec(),
+            "PASSWORD_MIN_NUMERIC_CHARS".as_bytes().to_vec(),
+            "PASSWORD_MIN_SPECIAL_CHARS".as_bytes().to_vec(),
+            "PASSWORD_MIN_AGE_DAYS".as_bytes().to_vec(),
+            "PASSWORD_MAX_AGE_DAYS".as_bytes().to_vec(),
+            "PASSWORD_MAX_RETRIES".as_bytes().to_vec(),
+            "PASSWORD_LOCKOUT_TIME_MINS".as_bytes().to_vec(),
+            "PASSWORD_HISTORY".as_bytes().to_vec(),
+        ];
+
+        let min_length = format!("{}", password_policy.min_length);
+        let max_length = format!("{}", password_policy.max_length);
+        let min_upper_case_chars = format!("{}", password_policy.min_upper_case_chars);
+        let min_lower_case_chars = format!("{}", password_policy.min_lower_case_chars);
+        let min_numeric_chars = format!("{}", password_policy.min_numeric_chars);
+        let min_special_chars = format!("{}", password_policy.min_special_chars);
+        let min_age_days = format!("{}", password_policy.min_age_days);
+        let max_age_days = format!("{}", password_policy.max_age_days);
+        let max_retries = format!("{}", password_policy.max_retries);
+        let lockout_time_mins = format!("{}", password_policy.lockout_time_mins);
+        let history = format!("{}", password_policy.history);
+
+        let values = vec![
+            password_policy.name.as_bytes().to_vec(),
+            password_policy.comment.as_bytes().to_vec(),
+            min_length.as_bytes().to_vec(),
+            max_length.as_bytes().to_vec(),
+            min_upper_case_chars.as_bytes().to_vec(),
+            min_lower_case_chars.as_bytes().to_vec(),
+            min_numeric_chars.as_bytes().to_vec(),
+            min_special_chars.as_bytes().to_vec(),
+            min_age_days.as_bytes().to_vec(),
+            max_age_days.as_bytes().to_vec(),
+            max_retries.as_bytes().to_vec(),
+            lockout_time_mins.as_bytes().to_vec(),
+            history.as_bytes().to_vec(),
+        ];
+
+        let defaults = vec![
+            None,
+            None,
+            Some(DEFAULT_PASSWORD_MIN_LENGTH),
+            Some(DEFAULT_PASSWORD_MAX_LENGTH),
+            Some(DEFAULT_PASSWORD_MIN_CHARS),
+            Some(DEFAULT_PASSWORD_MIN_CHARS),
+            Some(DEFAULT_PASSWORD_MIN_CHARS),
+            Some(DEFAULT_PASSWORD_MIN_SPECIAL_CHARS),
+            Some(DEFAULT_PASSWORD_MIN_AGE_DAYS),
+            Some(DEFAULT_PASSWORD_MAX_AGE_DAYS),
+            Some(DEFAULT_PASSWORD_MAX_RETRIES),
+            Some(DEFAULT_PASSWORD_LOCKOUT_TIME_MINS),
+            Some(DEFAULT_PASSWORD_HISTORY),
+        ];
+
+        let descriptions = vec![
+            "Name of password policy.".as_bytes().to_vec(),
+            "Comment of password policy.".as_bytes().to_vec(),
+            "Minimum length of new password.".as_bytes().to_vec(),
+            "Maximum length of new password.".as_bytes().to_vec(),
+            "Minimum number of uppercase characters in new password.".as_bytes().to_vec(),
+            "Minimum number of lowercase characters in new password.".as_bytes().to_vec(),
+            "Minimum number of numeric characters in new password.".as_bytes().to_vec(),
+            "Minimum number of special characters in new password.".as_bytes().to_vec(),
+            "Period after a password is changed during which a password cannot be changed again, in days.".as_bytes().to_vec(),
+            "Period after which password must be changed, in days.".as_bytes().to_vec(),
+            "Number of attempts users have to enter the correct password before their account is locked.".as_bytes().to_vec(),
+            "Period of time for which users will be locked after entering their password incorrectly many times (specified by MAX_RETRIES), in minutes.".as_bytes().to_vec(),
+            "Number of most recent passwords that may not be repeated by the user.".as_bytes().to_vec(),
+        ];
+
+        PipelineBuildResult::from_blocks(vec![DataBlock::new_from_columns(vec![
+            StringType::from_data(properties),
+            StringType::from_data(values),
+            UInt64Type::from_opt_data(defaults),
+            StringType::from_data(descriptions),
+        ])])
+    }
+}
diff --git a/src/query/service/src/interpreters/interpreter_password_policy_drop.rs b/src/query/service/src/interpreters/interpreter_password_policy_drop.rs
new file mode 100644
index 0000000000000..df4f868f105cd
--- /dev/null
+++ b/src/query/service/src/interpreters/interpreter_password_policy_drop.rs
@@ -0,0 +1,60 @@
+// Copyright 2021 Datafuse Labs
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::sync::Arc;
+
+use databend_common_exception::Result;
+use databend_common_sql::plans::DropPasswordPolicyPlan;
+use databend_common_users::UserApiProvider;
+use log::debug;
+
+use crate::interpreters::Interpreter;
+use crate::pipelines::PipelineBuildResult;
+use crate::sessions::QueryContext;
+use crate::sessions::TableContext;
+
+#[derive(Debug)]
+pub struct DropPasswordPolicyInterpreter {
+    ctx: Arc<QueryContext>,
+    plan: DropPasswordPolicyPlan,
+}
+
+impl DropPasswordPolicyInterpreter {
+    pub fn try_create(ctx: Arc<QueryContext>, plan: DropPasswordPolicyPlan) -> Result<Self> {
+        Ok(DropPasswordPolicyInterpreter { ctx, plan })
+    }
+}
+
+#[async_trait::async_trait]
+impl Interpreter for DropPasswordPolicyInterpreter {
+    fn name(&self) -> &str {
+        "DropPasswordPolicyInterpreter"
+    }
+
+    #[minitrace::trace]
+    #[async_backtrace::framed]
+    async fn execute2(&self) -> Result<PipelineBuildResult> {
+        debug!("ctx.id" = self.ctx.get_id().as_str(); "drop_password_policy_execute");
+
+        let plan = self.plan.clone();
+        let tenant = self.ctx.get_tenant();
+
+        let user_mgr = UserApiProvider::instance();
+        user_mgr
+            .drop_password_policy(&tenant, plan.name.as_str(), plan.if_exists)
+            .await?;
+
+        Ok(PipelineBuildResult::create())
+    }
+}
diff --git a/src/query/service/src/interpreters/mod.rs b/src/query/service/src/interpreters/mod.rs
index bbe152bdbfd84..35cf8d28d5c3b 100644
--- a/src/query/service/src/interpreters/mod.rs
+++ b/src/query/service/src/interpreters/mod.rs
@@ -55,6 +55,10 @@ mod interpreter_network_policy_alter;
 mod interpreter_network_policy_create;
 mod interpreter_network_policy_desc;
 mod interpreter_network_policy_drop;
+mod interpreter_password_policy_alter;
+mod interpreter_password_policy_create;
+mod interpreter_password_policy_desc;
+mod interpreter_password_policy_drop;
 mod interpreter_presign;
 mod interpreter_privilege_grant;
 mod interpreter_privilege_revoke;
@@ -155,6 +159,10 @@ pub use interpreter_network_policy_alter::AlterNetworkPolicyInterpreter;
 pub use interpreter_network_policy_create::CreateNetworkPolicyInterpreter;
 pub use interpreter_network_policy_desc::DescNetworkPolicyInterpreter;
 pub use interpreter_network_policy_drop::DropNetworkPolicyInterpreter;
+pub use interpreter_password_policy_alter::AlterPasswordPolicyInterpreter;
+pub use interpreter_password_policy_create::CreatePasswordPolicyInterpreter;
+pub use interpreter_password_policy_desc::DescPasswordPolicyInterpreter;
+pub use interpreter_password_policy_drop::DropPasswordPolicyInterpreter;
 pub use interpreter_privilege_grant::GrantPrivilegeInterpreter;
 pub use interpreter_privilege_revoke::RevokePrivilegeInterpreter;
 pub use interpreter_replace::ReplaceInterpreter;
diff --git a/src/query/service/tests/it/storages/testdata/columns_table.txt b/src/query/service/tests/it/storages/testdata/columns_table.txt
index f4c61bf33535b..4c2bda0dc14b2 100644
--- a/src/query/service/tests/it/storages/testdata/columns_table.txt
+++ b/src/query/service/tests/it/storages/testdata/columns_table.txt
@@ -49,6 +49,7 @@ DB.Table: 'system'.'columns', Table: columns-table_id:1, ver:0, Engine: SystemCo
 | 'command'                         | 'system'             | 'processes'           | 'String'              | 'VARCHAR'           | ''       | ''       | 'NO'     | ''       |
 | 'comment'                         | 'information_schema' | 'statistics'          | 'NULL'                | 'NULL'              | ''       | ''       | 'NO'     | ''       |
 | 'comment'                         | 'system'             | 'columns'             | 'String'              | 'VARCHAR'           | ''       | ''       | 'NO'     | ''       |
+| 'comment'                         | 'system'             | 'password_policies'   | 'String'              | 'VARCHAR'           | ''       | ''       | 'NO'     | ''       |
 | 'comment'                         | 'system'             | 'stages'              | 'String'              | 'VARCHAR'           | ''       | ''       | 'NO'     | ''       |
 | 'comment'                         | 'system'             | 'streams'             | 'String'              | 'VARCHAR'           | ''       | ''       | 'NO'     | ''       |
 | 'comment'                         | 'system'             | 'task_history'        | 'Nullable(String)'    | 'VARCHAR'           | ''       | ''       | 'YES'    | ''       |
@@ -66,6 +67,7 @@ DB.Table: 'system'.'columns', Table: columns-table_id:1, ver:0, Engine: SystemCo
 | 'created_on'                      | 'system'             | 'background_tasks'    | 'Timestamp'           | 'TIMESTAMP'         | ''       | ''       | 'NO'     | ''       |
 | 'created_on'                      | 'system'             | 'indexes'             | 'Timestamp'           | 'TIMESTAMP'         | ''       | ''       | 'NO'     | ''       |
 | 'created_on'                      | 'system'             | 'locks'               | 'Timestamp'           | 'TIMESTAMP'         | ''       | ''       | 'NO'     | ''       |
+| 'created_on'                      | 'system'             | 'password_policies'   | 'Timestamp'           | 'TIMESTAMP'         | ''       | ''       | 'NO'     | ''       |
 | 'created_on'                      | 'system'             | 'stages'              | 'Timestamp'           | 'TIMESTAMP'         | ''       | ''       | 'NO'     | ''       |
 | 'created_on'                      | 'system'             | 'streams'             | 'Timestamp'           | 'TIMESTAMP'         | ''       | ''       | 'NO'     | ''       |
 | 'created_on'                      | 'system'             | 'tables'              | 'Timestamp'           | 'TIMESTAMP'         | ''       | ''       | 'NO'     | ''       |
@@ -214,6 +216,7 @@ DB.Table: 'system'.'columns', Table: columns-table_id:1, ver:0, Engine: SystemCo
 | 'name'                            | 'system'             | 'functions'           | 'String'              | 'VARCHAR'           | ''       | ''       | 'NO'     | ''       |
 | 'name'                            | 'system'             | 'indexes'             | 'String'              | 'VARCHAR'           | ''       | ''       | 'NO'     | ''       |
 | 'name'                            | 'system'             | 'malloc_stats_totals' | 'String'              | 'VARCHAR'           | ''       | ''       | 'NO'     | ''       |
+| 'name'                            | 'system'             | 'password_policies'   | 'String'              | 'VARCHAR'           | ''       | ''       | 'NO'     | ''       |
 | 'name'                            | 'system'             | 'roles'               | 'String'              | 'VARCHAR'           | ''       | ''       | 'NO'     | ''       |
 | 'name'                            | 'system'             | 'settings'            | 'String'              | 'VARCHAR'           | ''       | ''       | 'NO'     | ''       |
 | 'name'                            | 'system'             | 'stages'              | 'String'              | 'VARCHAR'           | ''       | ''       | 'NO'     | ''       |
@@ -253,6 +256,7 @@ DB.Table: 'system'.'columns', Table: columns-table_id:1, ver:0, Engine: SystemCo
 | 'operator_id'                     | 'system'             | 'query_profile'       | 'UInt32'              | 'INT UNSIGNED'      | ''       | ''       | 'NO'     | ''       |
 | 'operator_id'                     | 'system'             | 'query_summary'       | 'UInt32'              | 'INT UNSIGNED'      | ''       | ''       | 'NO'     | ''       |
 | 'operator_type'                   | 'system'             | 'query_summary'       | 'String'              | 'VARCHAR'           | ''       | ''       | 'NO'     | ''       |
+| 'options'                         | 'system'             | 'password_policies'   | 'String'              | 'VARCHAR'           | ''       | ''       | 'NO'     | ''       |
 | 'ordinal_position'                | 'information_schema' | 'columns'             | 'UInt8'               | 'TINYINT UNSIGNED'  | ''       | ''       | 'NO'     | ''       |
 | 'ordinal_position'                | 'information_schema' | 'key_column_usage'    | 'NULL'                | 'NULL'              | ''       | ''       | 'NO'     | ''       |
 | 'owner'                           | 'system'             | 'databases'           | 'Nullable(String)'    | 'VARCHAR'           | ''       | ''       | 'YES'    | ''       |
@@ -381,6 +385,7 @@ DB.Table: 'system'.'columns', Table: columns-table_id:1, ver:0, Engine: SystemCo
 | 'type'                            | 'system'             | 'settings'            | 'String'              | 'VARCHAR'           | ''       | ''       | 'NO'     | ''       |
 | 'updated_on'                      | 'system'             | 'background_tasks'    | 'Timestamp'           | 'TIMESTAMP'         | ''       | ''       | 'NO'     | ''       |
 | 'updated_on'                      | 'system'             | 'indexes'             | 'Nullable(Timestamp)' | 'TIMESTAMP'         | ''       | ''       | 'YES'    | ''       |
+| 'updated_on'                      | 'system'             | 'password_policies'   | 'Nullable(Timestamp)' | 'TIMESTAMP'         | ''       | ''       | 'YES'    | ''       |
 | 'updated_on'                      | 'system'             | 'streams'             | 'Timestamp'           | 'TIMESTAMP'         | ''       | ''       | 'NO'     | ''       |
 | 'updated_on'                      | 'system'             | 'tables'              | 'Timestamp'           | 'TIMESTAMP'         | ''       | ''       | 'NO'     | ''       |
 | 'updated_on'                      | 'system'             | 'tables_with_history' | 'Timestamp'           | 'TIMESTAMP'         | ''       | ''       | 'NO'     | ''       |
diff --git a/src/query/sql/Cargo.toml b/src/query/sql/Cargo.toml
index 398039106d162..e58ce6b204df6 100644
--- a/src/query/sql/Cargo.toml
+++ b/src/query/sql/Cargo.toml
@@ -74,6 +74,7 @@ num-traits = "0.2.15"
 opendal = { workspace = true }
 ordered-float = { workspace = true }
 parking_lot = { workspace = true }
+passwords = { version = "3.1.16", features = ["common-password"] }
 percent-encoding = "2"
 regex = { workspace = true }
 roaring = "0.10.1"
diff --git a/src/query/sql/src/planner/binder/binder.rs b/src/query/sql/src/planner/binder/binder.rs
index 166cfd8e2d45b..6dae835b03e93 100644
--- a/src/query/sql/src/planner/binder/binder.rs
+++ b/src/query/sql/src/planner/binder/binder.rs
@@ -557,6 +557,19 @@ impl<'a> Binder {
             Statement::ShowNetworkPolicies => {
                 self.bind_show_network_policies().await?
             }
+            Statement::CreatePasswordPolicy(stmt) => {
+                self.bind_create_password_policy(stmt).await?
+            }
+            Statement::AlterPasswordPolicy(stmt) => {
+                self.bind_alter_password_policy(stmt).await?
+            }
+            Statement::DropPasswordPolicy(stmt) => {
+                self.bind_drop_password_policy(stmt).await?
+            }
+            Statement::DescPasswordPolicy(stmt) => {
+                self.bind_desc_password_policy(stmt).await?
+            }
+            Statement::ShowPasswordPolicies{ show_options } => self.bind_show_password_policies(bind_context, show_options).await?,
             Statement::CreateTask(stmt) => {
                 self.bind_create_task(stmt).await?
             }
diff --git a/src/query/sql/src/planner/binder/ddl/account.rs b/src/query/sql/src/planner/binder/ddl/account.rs
index 25df651327c31..1e6f3a7ba0f06 100644
--- a/src/query/sql/src/planner/binder/ddl/account.rs
+++ b/src/query/sql/src/planner/binder/ddl/account.rs
@@ -15,15 +15,18 @@
 use databend_common_ast::ast::AccountMgrLevel;
 use databend_common_ast::ast::AccountMgrSource;
 use databend_common_ast::ast::AlterUserStmt;
+use databend_common_ast::ast::AuthOption;
 use databend_common_ast::ast::CreateUserStmt;
 use databend_common_ast::ast::GrantStmt;
 use databend_common_ast::ast::RevokeStmt;
+use databend_common_exception::ErrorCode;
 use databend_common_exception::Result;
 use databend_common_meta_app::principal::AuthInfo;
 use databend_common_meta_app::principal::GrantObject;
 use databend_common_meta_app::principal::UserOption;
 use databend_common_meta_app::principal::UserPrivilegeSet;
 use databend_common_users::UserApiProvider;
+use passwords::analyzer;
 
 use crate::plans::AlterUserPlan;
 use crate::plans::CreateUserPlan;
@@ -161,6 +164,8 @@ impl Binder {
         for option in user_options {
             option.apply(&mut user_option);
         }
+        self.verify_password(&user_option, auth_option).await?;
+
         let plan = CreateUserPlan {
             user: user.clone(),
             auth_info: AuthInfo::create2(&auth_option.auth_type, &auth_option.password)?,
@@ -189,8 +194,15 @@ impl Binder {
                 .await?
         };
 
+        let mut user_option = user_info.option.clone();
+        for option in user_options {
+            option.apply(&mut user_option);
+        }
+
         // None means no change to make
         let new_auth_info = if let Some(auth_option) = &auth_option {
+            // verify the password if changed
+            self.verify_password(&user_option, auth_option).await?;
             let auth_info = user_info
                 .auth_info
                 .alter2(&auth_option.auth_type, &auth_option.password)?;
@@ -203,10 +215,6 @@ impl Binder {
             None
         };
 
-        let mut user_option = user_info.option.clone();
-        for option in user_options {
-            option.apply(&mut user_option);
-        }
         let new_user_option = if user_option == user_info.option {
             None
         } else {
@@ -220,4 +228,73 @@ impl Binder {
 
         Ok(Plan::AlterUser(Box::new(plan)))
     }
+
+    // Verify the password according to the options of the password policy
+    #[async_backtrace::framed]
+    async fn verify_password(
+        &mut self,
+        user_option: &UserOption,
+        auth_option: &AuthOption,
+    ) -> Result<()> {
+        if let (Some(name), Some(password)) = (user_option.password_policy(), &auth_option.password)
+        {
+            if let Ok(password_policy) = UserApiProvider::instance()
+                .get_password_policy(&self.ctx.get_tenant(), name)
+                .await
+            {
+                let analyzed = analyzer::analyze(password);
+
+                let mut invalids = Vec::new();
+                if analyzed.length() < password_policy.min_length as usize
+                    || analyzed.length() > password_policy.max_length as usize
+                {
+                    invalids.push(format!(
+                        "expect length range {} to {}, but got {}",
+                        password_policy.min_length,
+                        password_policy.max_length,
+                        analyzed.length()
+                    ));
+                }
+                if analyzed.uppercase_letters_count()
+                    < password_policy.min_upper_case_chars as usize
+                {
+                    invalids.push(format!(
+                        "expect {} uppercase chars, but got {}",
+                        password_policy.min_upper_case_chars,
+                        analyzed.uppercase_letters_count()
+                    ));
+                }
+                if analyzed.lowercase_letters_count()
+                    < password_policy.min_lower_case_chars as usize
+                {
+                    invalids.push(format!(
+                        "expect {} lowercase chars, but got {}",
+                        password_policy.min_lower_case_chars,
+                        analyzed.lowercase_letters_count()
+                    ));
+                }
+                if analyzed.numbers_count() < password_policy.min_numeric_chars as usize {
+                    invalids.push(format!(
+                        "expect {} numeric chars, but got {}",
+                        password_policy.min_numeric_chars,
+                        analyzed.numbers_count()
+                    ));
+                }
+                if analyzed.symbols_count() < password_policy.min_special_chars as usize {
+                    invalids.push(format!(
+                        "expect {} special chars, but got {}",
+                        password_policy.min_special_chars,
+                        analyzed.symbols_count()
+                    ));
+                }
+                if !invalids.is_empty() {
+                    return Err(ErrorCode::InvalidPassword(format!(
+                        "Invalid password: {}",
+                        invalids.join(", ")
+                    )));
+                }
+            }
+        }
+        Ok(())
+    }
 }
diff --git a/src/query/sql/src/planner/binder/ddl/mod.rs b/src/query/sql/src/planner/binder/ddl/mod.rs
index f20c2c98c2399..b38e29f239691 100644
--- a/src/query/sql/src/planner/binder/ddl/mod.rs
+++ b/src/query/sql/src/planner/binder/ddl/mod.rs
@@ -20,6 +20,7 @@ mod data_mask;
 mod database;
 mod index;
 mod network_policy;
+mod password_policy;
 mod role;
 mod share;
 mod stage;
diff --git a/src/query/sql/src/planner/binder/ddl/password_policy.rs b/src/query/sql/src/planner/binder/ddl/password_policy.rs
new file mode 100644
index 0000000000000..4ea58e3f63fb3
--- /dev/null
+++ b/src/query/sql/src/planner/binder/ddl/password_policy.rs
@@ -0,0 +1,115 @@
+// Copyright 2021 Datafuse Labs
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use databend_common_ast::ast::*;
+use databend_common_exception::Result;
+
+use crate::binder::show::get_show_options;
+use crate::binder::Binder;
+use crate::plans::AlterPasswordPolicyPlan;
+use crate::plans::CreatePasswordPolicyPlan;
+use crate::plans::DescPasswordPolicyPlan;
+use crate::plans::DropPasswordPolicyPlan;
+use crate::plans::Plan;
+use crate::plans::RewriteKind;
+use crate::BindContext;
+
+impl Binder {
+    #[async_backtrace::framed]
+    pub(in crate::planner::binder) async fn bind_create_password_policy(
+        &mut self,
+        stmt: &CreatePasswordPolicyStmt,
+    ) -> Result<Plan> {
+        let CreatePasswordPolicyStmt {
+            if_not_exists,
+            name,
+            set_options,
+        } = stmt;
+
+        let tenant = self.ctx.get_tenant();
+        let plan = CreatePasswordPolicyPlan {
+            if_not_exists: *if_not_exists,
+            tenant,
+            name: name.to_string(),
+            set_options: set_options.clone(),
+        };
+        Ok(Plan::CreatePasswordPolicy(Box::new(plan)))
+    }
+
+    #[async_backtrace::framed]
+    pub(in crate::planner::binder) async fn bind_alter_password_policy(
+        &mut self,
+        stmt: &AlterPasswordPolicyStmt,
+    ) -> Result<Plan> {
+        let AlterPasswordPolicyStmt {
+            if_exists,
+            name,
+            action,
+        } = stmt;
+
+        let tenant = self.ctx.get_tenant();
+        let plan = AlterPasswordPolicyPlan {
+            if_exists: *if_exists,
+            tenant,
+            name: name.to_string(),
+            action: action.clone(),
+        };
+        Ok(Plan::AlterPasswordPolicy(Box::new(plan)))
+    }
+
+    #[async_backtrace::framed]
+    pub(in crate::planner::binder) async fn bind_drop_password_policy(
+        &mut self,
+        stmt: &DropPasswordPolicyStmt,
+    ) -> Result<Plan> {
+        let DropPasswordPolicyStmt { if_exists, name } = stmt;
+
+        let tenant = self.ctx.get_tenant();
+        let plan = DropPasswordPolicyPlan {
+            if_exists: *if_exists,
+            tenant,
+            name: name.to_string(),
+        };
+        Ok(Plan::DropPasswordPolicy(Box::new(plan)))
+    }
+
+    #[async_backtrace::framed]
+    pub(in crate::planner::binder) async fn bind_desc_password_policy(
+        &mut self,
+        stmt: &DescPasswordPolicyStmt,
+    ) -> Result<Plan> {
+        let DescPasswordPolicyStmt { name } = stmt;
+
+        let plan = DescPasswordPolicyPlan {
+            name: name.to_string(),
+        };
+        Ok(Plan::DescPasswordPolicy(Box::new(plan)))
+    }
+
+    #[async_backtrace::framed]
+    pub(in crate::planner::binder) async fn bind_show_password_policies(
+        &mut self,
+        bind_context: &mut BindContext,
+        show_options: &Option<ShowOptions>,
+    ) -> Result<Plan> {
+        let (show_limit, limit_str) = get_show_options(show_options, None);
+        let query = format!(
+            "SELECT name, comment, options FROM system.password_policies {} order by name {}",
+            show_limit, limit_str,
+        );
+
+        self.bind_rewrite_to_query(bind_context, &query, RewriteKind::ShowPasswordPolicies)
+            .await
+    }
+}
diff --git a/src/query/sql/src/planner/binder/show.rs b/src/query/sql/src/planner/binder/show.rs
index 6c1b2e12a7480..b2fcd17505ae2 100644
--- a/src/query/sql/src/planner/binder/show.rs
+++ b/src/query/sql/src/planner/binder/show.rs
@@ -134,7 +134,7 @@ impl Binder {
             show_limit, limit_str,
         );
 
-        self.bind_rewrite_to_query(bind_context, &query, RewriteKind::ShowProcessList)
+        self.bind_rewrite_to_query(bind_context, &query, RewriteKind::ShowIndexes)
             .await
     }
 
@@ -167,17 +167,21 @@ impl Binder {
     }
 }
 
-fn get_show_options(show_options: &Option<ShowOptions>, col: Option<String>) -> (String, String) {
+pub(crate) fn get_show_options(
+    show_options: &Option<ShowOptions>,
+    col: Option<String>,
+) -> (String, String) {
     let mut show_limit = String::new();
     let mut limit_str = String::new();
 
     if let Some(show_option) = show_options {
         match &show_option.show_limit {
             Some(ShowLimit::Like { pattern }) => {
+                // convert like pattern to lowercase to uses case-insensitive pattern matching
                 if let Some(col) = &col {
-                    show_limit = format!("WHERE {} LIKE '{}'", col, pattern);
+                    show_limit = format!("WHERE LOWER({}) LIKE '{}'", col, pattern.to_lowercase());
                 } else {
-                    show_limit = format!("WHERE name LIKE '{}'", pattern);
+                    show_limit = format!("WHERE LOWER(name) LIKE '{}'", pattern.to_lowercase());
                 }
             }
             Some(ShowLimit::Where { selection }) => {
diff --git a/src/query/sql/src/planner/format/display_plan.rs b/src/query/sql/src/planner/format/display_plan.rs
index 4618bc929f846..516ee0492de60 100644
--- a/src/query/sql/src/planner/format/display_plan.rs
+++ b/src/query/sql/src/planner/format/display_plan.rs
@@ -177,6 +177,12 @@ impl Plan {
             Plan::DescNetworkPolicy(_) => Ok("DescNetworkPolicy".to_string()),
             Plan::ShowNetworkPolicies(_) => Ok("ShowNetworkPolicies".to_string()),
 
+            // password policy
+            Plan::CreatePasswordPolicy(_) => Ok("CreatePasswordPolicy".to_string()),
+            Plan::AlterPasswordPolicy(_) => Ok("AlterPasswordPolicy".to_string()),
+            Plan::DropPasswordPolicy(_) => Ok("DropPasswordPolicy".to_string()),
+            Plan::DescPasswordPolicy(_) => Ok("DescPasswordPolicy".to_string()),
+
             // task
             Plan::CreateTask(_) => Ok("CreateTask".to_string()),
             Plan::DropTask(_) => Ok("DropTask".to_string()),
diff --git a/src/query/sql/src/planner/plans/ddl/account.rs b/src/query/sql/src/planner/plans/ddl/account.rs
index 76c54d7404d64..2f0d280f4c64b 100644
--- a/src/query/sql/src/planner/plans/ddl/account.rs
+++ b/src/query/sql/src/planner/plans/ddl/account.rs
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+use databend_common_ast::ast::AlterPasswordAction;
+use databend_common_ast::ast::PasswordSetOptions;
 use databend_common_expression::types::DataType;
 use databend_common_expression::types::NumberDataType;
 use databend_common_expression::DataField;
@@ -195,3 +197,60 @@ impl ShowNetworkPoliciesPlan {
         ])
     }
 }
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct CreatePasswordPolicyPlan {
+    pub if_not_exists: bool,
+    pub tenant: String,
+    pub name: String,
+    pub set_options: PasswordSetOptions,
+}
+
+impl CreatePasswordPolicyPlan {
+    pub fn schema(&self) -> DataSchemaRef {
+        DataSchemaRefExt::create(vec![])
+    }
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct AlterPasswordPolicyPlan {
+    pub if_exists: bool,
+    pub tenant: String,
+    pub name: String,
+    pub action: AlterPasswordAction,
+}
+
+impl AlterPasswordPolicyPlan {
+    pub fn schema(&self) -> DataSchemaRef {
+        DataSchemaRefExt::create(vec![])
+    }
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct DropPasswordPolicyPlan {
+    pub if_exists: bool,
+    pub tenant: String,
+    pub name: String,
+}
+
+impl DropPasswordPolicyPlan {
+    pub fn schema(&self) -> DataSchemaRef {
+        DataSchemaRefExt::create(vec![])
+    }
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct DescPasswordPolicyPlan {
+    pub name: String,
+}
+
+impl DescPasswordPolicyPlan {
+    pub fn schema(&self) -> DataSchemaRef {
+        DataSchemaRefExt::create(vec![
+            DataField::new("Property", DataType::String),
+            DataField::new("Value", DataType::String),
+            DataField::new("Default", DataType::Nullable(Box::new(DataType::String))),
+            DataField::new("Description", DataType::String),
+        ])
+    }
+}
diff --git a/src/query/sql/src/planner/plans/plan.rs b/src/query/sql/src/planner/plans/plan.rs
index 7f5366a53ee7a..01b54814def9b 100644
--- a/src/query/sql/src/planner/plans/plan.rs
+++ b/src/query/sql/src/planner/plans/plan.rs
@@ -29,6 +29,7 @@ use crate::optimizer::SExpr;
 use crate::plans::copy_into_location::CopyIntoLocationPlan;
 use crate::plans::AddTableColumnPlan;
 use crate::plans::AlterNetworkPolicyPlan;
+use crate::plans::AlterPasswordPolicyPlan;
 use crate::plans::AlterShareTenantsPlan;
 use crate::plans::AlterTableClusterKeyPlan;
 use crate::plans::AlterTaskPlan;
@@ -46,6 +47,7 @@ use crate::plans::CreateDatamaskPolicyPlan;
 use crate::plans::CreateFileFormatPlan;
 use crate::plans::CreateIndexPlan;
 use crate::plans::CreateNetworkPolicyPlan;
+use crate::plans::CreatePasswordPolicyPlan;
 use crate::plans::CreateRolePlan;
 use crate::plans::CreateShareEndpointPlan;
 use crate::plans::CreateSharePlan;
@@ -61,6 +63,7 @@ use crate::plans::DeletePlan;
 use crate::plans::DescConnectionPlan;
 use crate::plans::DescDatamaskPolicyPlan;
 use crate::plans::DescNetworkPolicyPlan;
+use crate::plans::DescPasswordPolicyPlan;
 use crate::plans::DescSharePlan;
 use crate::plans::DescribeTablePlan;
 use crate::plans::DescribeTaskPlan;
@@ -71,6 +74,7 @@ use crate::plans::DropDatamaskPolicyPlan;
 use crate::plans::DropFileFormatPlan;
 use crate::plans::DropIndexPlan;
 use crate::plans::DropNetworkPolicyPlan;
+use crate::plans::DropPasswordPolicyPlan;
 use crate::plans::DropRolePlan;
 use crate::plans::DropShareEndpointPlan;
 use crate::plans::DropSharePlan;
@@ -301,6 +305,12 @@ pub enum Plan {
     DescNetworkPolicy(Box<DescNetworkPolicyPlan>),
     ShowNetworkPolicies(Box<ShowNetworkPoliciesPlan>),
 
+    // Password policy
+    CreatePasswordPolicy(Box<CreatePasswordPolicyPlan>),
+    AlterPasswordPolicy(Box<AlterPasswordPolicyPlan>),
+    DropPasswordPolicy(Box<DropPasswordPolicyPlan>),
+    DescPasswordPolicy(Box<DescPasswordPolicyPlan>),
+
     // Task
     CreateTask(Box<CreateTaskPlan>),
     AlterTask(Box<AlterTaskPlan>),
@@ -337,6 +347,7 @@ pub enum RewriteKind {
     DescribeStage,
     ListStage,
     ShowRoles,
+    ShowPasswordPolicies,
 
     Call,
 }
@@ -407,11 +418,9 @@ impl Plan {
             Plan::CreateDatamaskPolicy(plan) => plan.schema(),
             Plan::DropDatamaskPolicy(plan) => plan.schema(),
             Plan::DescDatamaskPolicy(plan) => plan.schema(),
-            Plan::CreateNetworkPolicy(plan) => plan.schema(),
-            Plan::AlterNetworkPolicy(plan) => plan.schema(),
-            Plan::DropNetworkPolicy(plan) => plan.schema(),
             Plan::DescNetworkPolicy(plan) => plan.schema(),
             Plan::ShowNetworkPolicies(plan) => plan.schema(),
+            Plan::DescPasswordPolicy(plan) => plan.schema(),
             Plan::CopyIntoTable(plan) => plan.schema(),
             Plan::MergeInto(plan) => plan.schema(),
             Plan::CreateTask(plan) => plan.schema(),
@@ -455,6 +464,7 @@ impl Plan {
                 | Plan::DescDatamaskPolicy(_)
                 | Plan::DescNetworkPolicy(_)
                 | Plan::ShowNetworkPolicies(_)
+                | Plan::DescPasswordPolicy(_)
                 | Plan::CopyIntoTable(_)
                 | Plan::ShowTasks(_)
                 | Plan::DescribeTask(_)
diff --git a/src/query/storages/system/src/lib.rs b/src/query/storages/system/src/lib.rs
index 977884955a9b1..f7edbdbbdf07e 100644
--- a/src/query/storages/system/src/lib.rs
+++ b/src/query/storages/system/src/lib.rs
@@ -40,6 +40,7 @@ mod malloc_stats_table;
 mod malloc_stats_totals_table;
 mod metrics_table;
 mod one_table;
+mod password_policies_table;
 mod processes_table;
 mod processor_profile_table;
 mod query_cache_table;
@@ -87,6 +88,7 @@ pub use malloc_stats_table::MallocStatsTable;
 pub use malloc_stats_totals_table::MallocStatsTotalsTable;
 pub use metrics_table::MetricsTable;
 pub use one_table::OneTable;
+pub use password_policies_table::PasswordPoliciesTable;
 pub use processes_table::ProcessesTable;
 pub use processor_profile_table::ProcessorProfileTable;
 pub use query_cache_table::QueryCacheTable;
diff --git a/src/query/storages/system/src/password_policies_table.rs b/src/query/storages/system/src/password_policies_table.rs
new file mode 100644
index 0000000000000..dc83061ccc7c6
--- /dev/null
+++ b/src/query/storages/system/src/password_policies_table.rs
@@ -0,0 +1,130 @@
+// Copyright 2021 Datafuse Labs
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::sync::Arc;
+
+use databend_common_catalog::plan::PushDownInfo;
+use databend_common_catalog::table::Table;
+use databend_common_catalog::table_context::TableContext;
+use databend_common_exception::Result;
+use databend_common_expression::types::StringType;
+use databend_common_expression::types::TimestampType;
+use databend_common_expression::utils::FromData;
+use databend_common_expression::DataBlock;
+use databend_common_expression::TableDataType;
+use databend_common_expression::TableField;
+use databend_common_expression::TableSchemaRefExt;
+use databend_common_meta_app::schema::TableIdent;
+use databend_common_meta_app::schema::TableInfo;
+use databend_common_meta_app::schema::TableMeta;
+use databend_common_users::UserApiProvider;
+
+use crate::table::AsyncOneBlockSystemTable;
+use crate::table::AsyncSystemTable;
+
+pub struct PasswordPoliciesTable {
+    table_info: TableInfo,
+}
+
+#[async_trait::async_trait]
+impl AsyncSystemTable for PasswordPoliciesTable {
+    const NAME: &'static str = "system.password_policies";
+
+    fn get_table_info(&self) -> &TableInfo {
+        &self.table_info
+    }
+
+    #[async_backtrace::framed]
+    async fn get_full_data(
+        &self,
+        ctx: Arc<dyn TableContext>,
+        _push_downs: Option<PushDownInfo>,
+    ) -> Result<DataBlock> {
+        let tenant = ctx.get_tenant();
+        let password_policies = UserApiProvider::instance()
+            .get_password_policies(&tenant)
+            .await?;
+
+        let mut names = Vec::with_capacity(password_policies.len());
+        let mut comments = Vec::with_capacity(password_policies.len());
+        let mut options = Vec::with_capacity(password_policies.len());
+        let mut created_ons = Vec::with_capacity(password_policies.len());
+        let mut updated_ons = Vec::with_capacity(password_policies.len());
+        for password_policy in password_policies {
+            names.push(password_policy.name.as_bytes().to_vec());
+            comments.push(password_policy.comment.as_bytes().to_vec());
+
+            let values = vec![
+                format!("MIN_LENGTH={}", password_policy.min_length),
+                format!("MAX_LENGTH={}", password_policy.max_length),
+                format!(
+                    "MIN_UPPER_CASE_CHARS={}",
+                    password_policy.min_upper_case_chars
+                ),
+                format!(
+                    "MIN_LOWER_CASE_CHARS={}",
+                    password_policy.min_lower_case_chars
+                ),
+                format!("MIN_NUMERIC_CHARS={}", password_policy.min_numeric_chars),
+                format!("MIN_SPECIAL_CHARS={}", password_policy.min_special_chars),
+                format!("MIN_AGE_DAYS={}", password_policy.min_age_days),
+                format!("MAX_AGE_DAYS={}", password_policy.max_age_days),
+                format!("MAX_RETRIES={}", password_policy.max_retries),
+                format!("LOCKOUT_TIME_MINS={}", password_policy.lockout_time_mins),
+                format!("HISTORY={}", password_policy.history),
+            ];
+            let option = values.join(", ");
+            options.push(option.as_bytes().to_vec());
+
+            created_ons.push(password_policy.create_on.timestamp_micros());
+            updated_ons.push(password_policy.update_on.map(|u| u.timestamp_micros()));
+        }
+
+        Ok(DataBlock::new_from_columns(vec![
+            StringType::from_data(names),
+            StringType::from_data(comments),
+            StringType::from_data(options),
+            TimestampType::from_data(created_ons),
+            TimestampType::from_opt_data(updated_ons),
+        ]))
+    }
+}
+
+impl PasswordPoliciesTable {
+    pub fn create(table_id: u64) -> Arc<dyn Table> {
+        let schema = TableSchemaRefExt::create(vec![
+            TableField::new("name", TableDataType::String),
+            TableField::new("comment", TableDataType::String),
+            TableField::new("options", TableDataType::String),
+            TableField::new("created_on", TableDataType::Timestamp),
+            TableField::new(
+                "updated_on",
+                TableDataType::Nullable(Box::new(TableDataType::Timestamp)),
+            ),
+        ]);
+
+        let table_info = TableInfo {
+            desc: "'system'.'password_policies'".to_string(),
+            name: "password_policies".to_string(),
+            ident: TableIdent::new(table_id, 0),
+            meta: TableMeta {
+                schema,
+                engine: "SystemPasswordPolicies".to_string(),
+                ..Default::default()
+            },
+            ..Default::default()
+        };
+        AsyncOneBlockSystemTable::create(PasswordPoliciesTable { table_info })
+    }
+}
diff --git a/src/query/users/src/lib.rs b/src/query/users/src/lib.rs
index f3f605d43f031..effe3ac97b395 100644
--- a/src/query/users/src/lib.rs
+++ b/src/query/users/src/lib.rs
@@ -20,6 +20,7 @@ extern crate core;
 
 mod jwt;
 mod network_policy;
+mod password_policy;
 mod role_mgr;
 mod user;
 mod user_api;
@@ -36,6 +37,7 @@ pub mod role_cache_mgr;
 pub mod role_util;
 
 pub use jwt::*;
+pub use password_policy::*;
 pub use role_cache_mgr::RoleCacheManager;
 pub use role_mgr::BUILTIN_ROLE_ACCOUNT_ADMIN;
 pub use role_mgr::BUILTIN_ROLE_PUBLIC;
diff --git a/src/query/users/src/password_policy.rs b/src/query/users/src/password_policy.rs
new file mode 100644
index 0000000000000..2fc98cdff50e1
--- /dev/null
+++ b/src/query/users/src/password_policy.rs
@@ -0,0 +1,354 @@
+// Copyright 2021 Datafuse Labs
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use chrono::Utc;
+use databend_common_exception::ErrorCode;
+use databend_common_exception::Result;
+use databend_common_management::PasswordPolicyApi;
+use databend_common_meta_app::principal::PasswordPolicy;
+use databend_common_meta_types::MatchSeq;
+
+use crate::UserApiProvider;
+
+// default value of password policy options
+pub const DEFAULT_PASSWORD_MIN_LENGTH: u64 = 8;
+pub const DEFAULT_PASSWORD_MAX_LENGTH: u64 = 256;
+pub const DEFAULT_PASSWORD_MIN_CHARS: u64 = 1;
+pub const DEFAULT_PASSWORD_MIN_SPECIAL_CHARS: u64 = 0;
+pub const DEFAULT_PASSWORD_MIN_AGE_DAYS: u64 = 0;
+pub const DEFAULT_PASSWORD_MAX_AGE_DAYS: u64 = 90;
+pub const DEFAULT_PASSWORD_MAX_RETRIES: u64 = 5;
+pub const DEFAULT_PASSWORD_LOCKOUT_TIME_MINS: u64 = 15;
+pub const DEFAULT_PASSWORD_HISTORY: u64 = 0;
+
+// minimal value of password policy options
+pub const MIN_PASSWORD_LENGTH: u64 = 8;
+pub const MIN_PASSWORD_CHARS: u64 = 0;
+pub const MIN_PASSWORD_AGE_DAYS: u64 = 0;
+pub const MIN_PASSWORD_MAX_RETRIES: u64 = 1;
+pub const MIN_PASSWORD_LOCKOUT_TIME_MINS: u64 = 1;
+pub const MIN_PASSWORD_HISTORY: u64 = 0;
+
+// maximum value of password policy options
+pub const MAX_PASSWORD_LENGTH: u64 = 256;
+pub const MAX_PASSWORD_CHARS: u64 = 256;
+pub const MAX_PASSWORD_AGE_DAYS: u64 = 999;
+pub const MAX_PASSWORD_MAX_RETRIES: u64 = 10;
+pub const MAX_PASSWORD_LOCKOUT_TIME_MINS: u64 = 999;
+pub const MAX_PASSWORD_HISTORY: u64 = 24;
+
+impl UserApiProvider {
+    // Add a new password policy.
+    #[async_backtrace::framed]
+    pub async fn add_password_policy(
+        &self,
+        tenant: &str,
+        password_policy: PasswordPolicy,
+        if_not_exists: bool,
+    ) -> Result<u64> {
+        check_password_policy(&password_policy)?;
+
+        if if_not_exists
+            && self
+                .exists_password_policy(tenant, password_policy.name.as_str())
+                .await?
+        {
+            return Ok(0);
+        }
+
+        let client = self.get_password_policy_api_client(tenant)?;
+        let add_password_policy = client.add_password_policy(password_policy);
+        match add_password_policy.await {
+            Ok(res) => Ok(res),
+            Err(e) => {
+                if if_not_exists && e.code() == ErrorCode::PASSWORD_POLICY_ALREADY_EXISTS {
+                    Ok(0)
+                } else {
+                    Err(e.add_message_back("(while add password policy)"))
+                }
+            }
+        }
+    }
+
+    // Update password policy.
+    #[async_backtrace::framed]
+    #[allow(clippy::too_many_arguments)]
+    pub async fn update_password_policy(
+        &self,
+        tenant: &str,
+        name: &str,
+        min_length: Option<u64>,
+        max_length: Option<u64>,
+        min_upper_case_chars: Option<u64>,
+        min_lower_case_chars: Option<u64>,
+        min_numeric_chars: Option<u64>,
+        min_special_chars: Option<u64>,
+        min_age_days: Option<u64>,
+        max_age_days: Option<u64>,
+        max_retries: Option<u64>,
+        lockout_time_mins: Option<u64>,
+        history: Option<u64>,
+        comment: Option<String>,
+        if_exists: bool,
+    ) -> Result<Option<u64>> {
+        let client = self.get_password_policy_api_client(tenant)?;
+        let seq_password_policy = match client.get_password_policy(name, MatchSeq::GE(0)).await {
+            Ok(seq_password_policy) => seq_password_policy,
+            Err(e) => {
+                if if_exists && e.code() == ErrorCode::UNKNOWN_PASSWORD_POLICY {
+                    return Ok(None);
+                } else {
+                    return Err(e.add_message_back(" (while alter password policy)"));
+                }
+            }
+        };
+
+        let seq = seq_password_policy.seq;
+        let mut password_policy = seq_password_policy.data;
+        if let Some(min_length) = min_length {
+            password_policy.min_length = min_length;
+        }
+        if let Some(max_length) = max_length {
+            password_policy.max_length = max_length;
+        }
+        if let Some(min_upper_case_chars) = min_upper_case_chars {
+            password_policy.min_upper_case_chars = min_upper_case_chars;
+        }
+        if let Some(min_lower_case_chars) = min_lower_case_chars {
+            password_policy.min_lower_case_chars = min_lower_case_chars;
+        }
+        if let Some(min_numeric_chars) = min_numeric_chars {
+            password_policy.min_numeric_chars = min_numeric_chars;
+        }
+        if let Some(min_special_chars) = min_special_chars {
+            password_policy.min_special_chars = min_special_chars;
+        }
+        if let Some(min_age_days) = min_age_days {
+            password_policy.min_age_days = min_age_days;
+        }
+        if let Some(max_age_days) = max_age_days {
+            password_policy.max_age_days = max_age_days;
+        }
+        if let Some(max_retries) = max_retries {
+            password_policy.max_retries = max_retries;
+        }
+        if let Some(lockout_time_mins) = lockout_time_mins {
+            password_policy.lockout_time_mins = lockout_time_mins;
+        }
+        if let Some(history) = history {
+            password_policy.history = history;
+        }
+        if let Some(comment) = comment {
+            password_policy.comment = comment;
+        }
+        check_password_policy(&password_policy)?;
+
+        password_policy.update_on = Some(Utc::now());
+
+        match client
+            .update_password_policy(password_policy, MatchSeq::Exact(seq))
+            .await
+        {
+            Ok(res) => Ok(Some(res)),
+            Err(e) => Err(e.add_message_back(" (while alter password policy).")),
+        }
+    }
+
+    // Drop a password policy by name.
+    #[async_backtrace::framed]
+    pub async fn drop_password_policy(
+        &self,
+        tenant: &str,
+        name: &str,
+        if_exists: bool,
+    ) -> Result<()> {
+        let user_infos = self.get_users(tenant).await?;
+        for user_info in user_infos {
+            if let Some(network_policy) = user_info.option.password_policy() {
+                if network_policy == name {
+                    return Err(ErrorCode::PasswordPolicyIsUsedByUser(format!(
+                        "password policy `{}` is used by user",
+                        name,
+                    )));
+                }
+            }
+        }
+
+        let client = self.get_password_policy_api_client(tenant)?;
+        match client.drop_password_policy(name, MatchSeq::GE(1)).await {
+            Ok(res) => Ok(res),
+            Err(e) => {
+                if if_exists && e.code() == ErrorCode::UNKNOWN_PASSWORD_POLICY {
+                    Ok(())
+                } else {
+                    Err(e.add_message_back(" (while drop password policy)"))
+                }
+            }
+        }
+    }
+
+    // Check whether a password policy is exist.
+    #[async_backtrace::framed]
+    pub async fn exists_password_policy(&self, tenant: &str, name: &str) -> Result<bool> {
+        match self.get_password_policy(tenant, name).await {
+            Ok(_) => Ok(true),
+            Err(e) => {
+                if e.code() == ErrorCode::UNKNOWN_PASSWORD_POLICY {
+                    Ok(false)
+                } else {
+                    Err(e)
+                }
+            }
+        }
+    }
+
+    // Get a password_policy by tenant.
+    #[async_backtrace::framed]
+    pub async fn get_password_policy(&self, tenant: &str, name: &str) -> Result<PasswordPolicy> {
+        let client = self.get_password_policy_api_client(tenant)?;
+        let password_policy = client
+            .get_password_policy(name, MatchSeq::GE(0))
+            .await?
+            .data;
+        Ok(password_policy)
+    }
+
+    // Get all password policies by tenant.
+    #[async_backtrace::framed]
+    pub async fn get_password_policies(&self, tenant: &str) -> Result<Vec<PasswordPolicy>> {
+        let client = self.get_password_policy_api_client(tenant)?;
+        let password_policies = client
+            .get_password_policies()
+            .await
+            .map_err(|e| e.add_message_back(" (while get password policies)."))?;
+        Ok(password_policies)
+    }
+}
+
+// Check whether the values of options in the password policy are valid
+fn check_password_policy(password_policy: &PasswordPolicy) -> Result<()> {
+    if !(MIN_PASSWORD_LENGTH..=MAX_PASSWORD_LENGTH).contains(&password_policy.min_length) {
+        return Err(ErrorCode::InvalidArgument(format!(
+            "invalid password min length, supported range: {} to {}, but got {}",
+            MIN_PASSWORD_LENGTH, MAX_PASSWORD_LENGTH, password_policy.min_length
+        )));
+    }
+
+    if !(MIN_PASSWORD_LENGTH..=MAX_PASSWORD_LENGTH).contains(&password_policy.max_length) {
+        return Err(ErrorCode::InvalidArgument(format!(
+            "invalid password max length, supported range: {} to {}, but got {}",
+            MIN_PASSWORD_LENGTH, MAX_PASSWORD_LENGTH, password_policy.max_length
+        )));
+    }
+
+    // min length can't greater than max length
+    if password_policy.min_length > password_policy.max_length {
+        return Err(ErrorCode::InvalidArgument(format!(
+            "invalid password length, min length must be less than max length, but got {} and {}",
+            password_policy.min_length, password_policy.max_length
+        )));
+    }
+
+    if !(MIN_PASSWORD_CHARS..=MAX_PASSWORD_CHARS).contains(&password_policy.min_upper_case_chars) {
+        return Err(ErrorCode::InvalidArgument(format!(
+            "invalid password min upper case chars, supported range: {} to {}, but got {}",
+            MIN_PASSWORD_CHARS, MAX_PASSWORD_CHARS, password_policy.min_upper_case_chars
+        )));
+    }
+
+    if !(MIN_PASSWORD_CHARS..=MAX_PASSWORD_CHARS).contains(&password_policy.min_lower_case_chars) {
+        return Err(ErrorCode::InvalidArgument(format!(
+            "invalid password min lower case chars, supported range: {} to {}, but got {}",
+            MIN_PASSWORD_CHARS, MAX_PASSWORD_CHARS, password_policy.min_lower_case_chars
+        )));
+    }
+
+    if !(MIN_PASSWORD_CHARS..=MAX_PASSWORD_CHARS).contains(&password_policy.min_numeric_chars) {
+        return Err(ErrorCode::InvalidArgument(format!(
+            "invalid password min numeric chars, supported range: {} to {}, but got {}",
+            MIN_PASSWORD_CHARS, MAX_PASSWORD_CHARS, password_policy.min_numeric_chars
+        )));
+    }
+
+    if !(MIN_PASSWORD_CHARS..=MAX_PASSWORD_CHARS).contains(&password_policy.min_special_chars) {
+        return Err(ErrorCode::InvalidArgument(format!(
+            "invalid password min special chars, supported range: {} to {}, but got {}",
+            MIN_PASSWORD_CHARS, MAX_PASSWORD_CHARS, password_policy.min_special_chars
+        )));
+    }
+
+    // sum min length of chars can't greater than max length
+    let char_length = password_policy.min_upper_case_chars
+        + password_policy.min_lower_case_chars
+        + password_policy.min_numeric_chars
+        + password_policy.min_special_chars;
+    if char_length > password_policy.max_length {
+        return Err(ErrorCode::InvalidArgument(format!(
+            "invalid password length, sum of min chars length must be less than max length, but got 
+            min upper case chars {}, min lower case chars {}, min numeric chars {}, min special chars {} 
+            and max length {}",
+            password_policy.min_upper_case_chars, password_policy.min_lower_case_chars,
+            password_policy.min_numeric_chars, password_policy.min_special_chars, password_policy.max_length
+        )));
+    }
+
+    if !(MIN_PASSWORD_AGE_DAYS..=MAX_PASSWORD_AGE_DAYS).contains(&password_policy.min_age_days) {
+        return Err(ErrorCode::InvalidArgument(format!(
+            "invalid password min age days, supported range: {} to {}, but got {}",
+            MIN_PASSWORD_AGE_DAYS, MAX_PASSWORD_AGE_DAYS, password_policy.min_age_days
+        )));
+    }
+
+    if !(MIN_PASSWORD_AGE_DAYS..=MAX_PASSWORD_AGE_DAYS).contains(&password_policy.max_age_days) {
+        return Err(ErrorCode::InvalidArgument(format!(
+            "invalid password max age days, supported range: {} to {}, but got {}",
+            MIN_PASSWORD_AGE_DAYS, MAX_PASSWORD_AGE_DAYS, password_policy.max_age_days
+        )));
+    }
+
+    if password_policy.min_age_days > password_policy.max_age_days {
+        return Err(ErrorCode::InvalidArgument(format!(
+            "invalid password age days, min age days must be less than max age days, but got {} and {}",
+            password_policy.min_age_days, password_policy.max_age_days
+        )));
+    }
+
+    if !(MIN_PASSWORD_MAX_RETRIES..=MAX_PASSWORD_MAX_RETRIES).contains(&password_policy.max_retries)
+    {
+        return Err(ErrorCode::InvalidArgument(format!(
+            "invalid password max retries, supported range: {} to {}, but got {}",
+            MIN_PASSWORD_MAX_RETRIES, MAX_PASSWORD_MAX_RETRIES, password_policy.max_retries
+        )));
+    }
+
+    if !(MIN_PASSWORD_LOCKOUT_TIME_MINS..=MAX_PASSWORD_LOCKOUT_TIME_MINS)
+        .contains(&password_policy.lockout_time_mins)
+    {
+        return Err(ErrorCode::InvalidArgument(format!(
+            "invalid password lockout time mins, supported range: {} to {}, but got {}",
+            MIN_PASSWORD_LOCKOUT_TIME_MINS,
+            MAX_PASSWORD_LOCKOUT_TIME_MINS,
+            password_policy.lockout_time_mins
+        )));
+    }
+
+    if !(MIN_PASSWORD_HISTORY..=MAX_PASSWORD_HISTORY).contains(&password_policy.history) {
+        return Err(ErrorCode::InvalidArgument(format!(
+            "invalid password history, supported range: {} to {}, but got {}",
+            MIN_PASSWORD_HISTORY, MAX_PASSWORD_HISTORY, password_policy.history
+        )));
+    }
+
+    Ok(())
+}
diff --git a/src/query/users/src/user_api.rs b/src/query/users/src/user_api.rs
index 9e73a401b3acf..96a71dcf3b487 100644
--- a/src/query/users/src/user_api.rs
+++ b/src/query/users/src/user_api.rs
@@ -24,6 +24,8 @@ use databend_common_management::FileFormatApi;
 use databend_common_management::FileFormatMgr;
 use databend_common_management::NetworkPolicyApi;
 use databend_common_management::NetworkPolicyMgr;
+use databend_common_management::PasswordPolicyApi;
+use databend_common_management::PasswordPolicyMgr;
 use databend_common_management::QuotaApi;
 use databend_common_management::QuotaMgr;
 use databend_common_management::RoleApi;
@@ -139,6 +141,16 @@ impl UserApiProvider {
         )?))
     }
 
+    pub fn get_password_policy_api_client(
+        &self,
+        tenant: &str,
+    ) -> Result<Arc<impl PasswordPolicyApi>> {
+        Ok(Arc::new(PasswordPolicyMgr::create(
+            self.client.clone(),
+            tenant,
+        )?))
+    }
+
     pub fn get_meta_store_client(&self) -> Arc<MetaStore> {
         Arc::new(self.meta.clone())
     }
diff --git a/src/query/users/src/user_mgr.rs b/src/query/users/src/user_mgr.rs
index 77eb4ab6118a5..92ced22a34f88 100644
--- a/src/query/users/src/user_mgr.rs
+++ b/src/query/users/src/user_mgr.rs
@@ -144,6 +144,14 @@ impl UserApiProvider {
                 )));
             }
         }
+        if let Some(name) = user_info.option.password_policy() {
+            if self.get_password_policy(tenant, name).await.is_err() {
+                return Err(ErrorCode::UnknownPasswordPolicy(format!(
+                    "password policy `{}` is not exist",
+                    name
+                )));
+            }
+        }
         if self.get_configured_user(&user_info.name).is_some() {
             return Err(ErrorCode::UserAlreadyExists(format!(
                 "Same name with configured user `{}`",
@@ -295,6 +303,14 @@ impl UserApiProvider {
                     )));
                 }
             }
+            if let Some(name) = user_option.password_policy() {
+                if self.get_password_policy(tenant, name).await.is_err() {
+                    return Err(ErrorCode::UnknownPasswordPolicy(format!(
+                        "password policy `{}` is not exist",
+                        name
+                    )));
+                }
+            }
         }
         if self.get_configured_user(&user.username).is_some() {
             return Err(ErrorCode::UserAlreadyExists(format!(
diff --git a/src/query/users/tests/it/mod.rs b/src/query/users/tests/it/mod.rs
index 3050ad50e21f4..70307c856b255 100644
--- a/src/query/users/tests/it/mod.rs
+++ b/src/query/users/tests/it/mod.rs
@@ -14,6 +14,7 @@
 
 mod jwt;
 mod network_policy;
+mod password_policy;
 mod role_cache_mgr;
 mod role_mgr;
 mod user_mgr;
diff --git a/src/query/users/tests/it/password_policy.rs b/src/query/users/tests/it/password_policy.rs
new file mode 100644
index 0000000000000..db5672104f9c2
--- /dev/null
+++ b/src/query/users/tests/it/password_policy.rs
@@ -0,0 +1,162 @@
+// Copyright 2021 Datafuse Labs
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use databend_common_base::base::tokio;
+use databend_common_exception::Result;
+use databend_common_grpc::RpcClientConf;
+use databend_common_meta_app::principal::AuthInfo;
+use databend_common_meta_app::principal::PasswordPolicy;
+use databend_common_meta_app::principal::UserIdentity;
+use databend_common_meta_app::principal::UserInfo;
+use databend_common_users::UserApiProvider;
+use pretty_assertions::assert_eq;
+
+#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
+async fn test_password_policy() -> Result<()> {
+    let conf = RpcClientConf::default();
+    let user_mgr = UserApiProvider::try_create_simple(conf).await?;
+
+    let tenant = "test";
+    let username = "test-user1";
+    let hostname = "%";
+    let pwd = "test-pwd";
+
+    let policy_name = "test_policy".to_string();
+
+    // add password policy
+    let password_policy = PasswordPolicy {
+        name: policy_name.clone(),
+        min_length: 12,
+        max_length: 24,
+        min_upper_case_chars: 2,
+        min_lower_case_chars: 2,
+        min_numeric_chars: 2,
+        min_special_chars: 2,
+        min_age_days: 1,
+        max_age_days: 30,
+        max_retries: 3,
+        lockout_time_mins: 30,
+        history: 5,
+        comment: "".to_string(),
+        create_on: Utc.with_ymd_and_hms(2023, 12, 19, 12, 0, 0).unwrap(),
+        update_on: None,
+    };
+
+    let res = user_mgr.add_password_policy(tenant, password_policy.clone(), false).await;
+    assert!(res.ok());
+
+    // invalid min length
+    let mut invalid_password_policy1 = password_policy.clone();
+    invalid_password_policy1.min_length = 0;
+    let res = user_mgr.add_password_policy(tenant, invalid_password_policy1, false).await;
+    assert!(res.is_err());
+
+    // invalid max length
+    let mut invalid_password_policy2 = password_policy.clone();
+    invalid_password_policy2.max_length = 260;
+    let res = user_mgr.add_password_policy(tenant, invalid_password_policy2, false).await;
+    assert!(res.is_err());
+
+    // invalid min length greater than max length
+    let mut invalid_password_policy3 = password_policy.clone();
+    invalid_password_policy3.min_length = 30;
+    invalid_password_policy3.max_length = 20;
+    let res = user_mgr.add_password_policy(tenant, invalid_password_policy2, false).await;
+    assert!(res.is_err());
+
+    // invalid min chars
+    let mut invalid_password_policy4 = password_policy.clone();
+    invalid_password_policy4.min_upper_case_chars = 270;
+    invalid_password_policy4.min_lower_case_chars = 271;
+    invalid_password_policy4.min_numeric_chars = 272;
+    invalid_password_policy4.min_special_chars = 273;
+    let res = user_mgr.add_password_policy(tenant, invalid_password_policy4, false).await;
+    assert!(res.is_err());
+
+    // invalid sum of upper chars, lower chars, numeric chars and special chars greater than max length
+    let mut invalid_password_policy5 = password_policy.clone();
+    invalid_password_policy5.max_length = 30;
+    invalid_password_policy5.min_upper_case_chars = 10;
+    invalid_password_policy5.min_lower_case_chars = 11;
+    invalid_password_policy5.min_numeric_chars = 12;
+    invalid_password_policy5.min_special_chars = 13;
+    let res = user_mgr.add_password_policy(tenant, invalid_password_policy5, false).await;
+    assert!(res.is_err());
+
+    // invalid min age days greater than max age days
+    let mut invalid_password_policy6 = password_policy.clone();
+    invalid_password_policy6.min_age_days = 20;
+    invalid_password_policy6.max_age_days = 10;
+    let res = user_mgr.add_password_policy(tenant, invalid_password_policy6, false).await;
+    assert!(res.is_err());
+
+    // invalid max retries
+    let mut invalid_password_policy7 = password_policy.clone();
+    invalid_password_policy7.max_retries = 20;
+    let res = user_mgr.add_password_policy(tenant, invalid_password_policy7, false).await;
+    assert!(res.is_err());
+
+    // invalid lockout time mins
+    let mut invalid_password_policy8 = password_policy.clone();
+    invalid_password_policy8.lockout_time_mins = 2000;
+    let res = user_mgr.add_password_policy(tenant, invalid_password_policy8, false).await;
+    assert!(res.is_err());
+
+    // invalid history
+    let mut invalid_password_policy9 = password_policy.clone();
+    invalid_password_policy9.history = 50;
+    let res = user_mgr.add_password_policy(tenant, invalid_password_policy9, false).await;
+    assert!(res.is_err());
+
+    // update password policy
+    let res = user_mgr.update_password_policy(tenant, &policy_name, 
+        Some(10),
+        Some(20),
+        Some(3),
+        Some(3),
+        Some(3),
+        Some(3),
+        Some(2),
+        Some(50),
+        Some(10),
+        Some(20),
+        Some(10),
+        None,
+        false).await;
+    assert!(res.ok());
+
+    // add user
+    let auth_info = AuthInfo::Password {
+        hash_value: Vec::from(pwd),
+        hash_method: PasswordHashMethod::Sha256,
+    };
+
+    let mut user_info = UserInfo::new(username, hostname, auth_info.clone());
+    let mut option = UserOption::empty();
+    option = option
+        .with_password_policy(Some(policy_name.clone()));
+    user_info.update_auth_option(None, Some(option))
+    user_mgr.add_user(tenant, user_info, false).await?;
+
+    // drop password policy
+    let res = user_mgr.drop_password_policy(tenant, policy_name.as_ref(), false).await;
+    assert!(res.is_err());
+
+    user_mgr.drop_user(tenant, user.clone(), false).await?;
+
+    let res = user_mgr.drop_password_policy(tenant, policy_name.as_ref(), false).await;
+    assert!(res.is_ok());
+
+    Ok(())
+}
diff --git a/tests/sqllogictests/suites/base/05_ddl/05_0034_ddl_password_policy.test b/tests/sqllogictests/suites/base/05_ddl/05_0034_ddl_password_policy.test
new file mode 100644
index 0000000000000..dd9a1eb407e7e
--- /dev/null
+++ b/tests/sqllogictests/suites/base/05_ddl/05_0034_ddl_password_policy.test
@@ -0,0 +1,134 @@
+statement ok
+DROP PASSWORD POLICY IF EXISTS test_policy
+
+statement ok
+DROP PASSWORD POLICY IF EXISTS default_policy
+
+statement error 2211
+DROP PASSWORD POLICY test_policy
+
+statement ok
+CREATE PASSWORD POLICY test_policy
+    PASSWORD_MIN_LENGTH = 12
+    PASSWORD_MAX_LENGTH = 24
+    PASSWORD_MIN_UPPER_CASE_CHARS = 2
+    PASSWORD_MIN_LOWER_CASE_CHARS = 2
+    PASSWORD_MIN_NUMERIC_CHARS = 2
+    PASSWORD_MIN_SPECIAL_CHARS = 2
+    PASSWORD_MIN_AGE_DAYS = 1
+    PASSWORD_MAX_AGE_DAYS = 30
+    PASSWORD_MAX_RETRIES = 3
+    PASSWORD_LOCKOUT_TIME_MINS = 30
+    PASSWORD_HISTORY = 5
+    COMMENT = 'test comment'
+
+query TTTT
+DESC PASSWORD POLICY test_policy
+----
+NAME test_policy NULL Name of password policy.
+COMMENT test comment NULL Comment of password policy.
+PASSWORD_MIN_LENGTH 12 8 Minimum length of new password.
+PASSWORD_MAX_LENGTH 24 256 Maximum length of new password.
+PASSWORD_MIN_UPPER_CASE_CHARS 2 1 Minimum number of uppercase characters in new password.
+PASSWORD_MIN_LOWER_CASE_CHARS 2 1 Minimum number of lowercase characters in new password.
+PASSWORD_MIN_NUMERIC_CHARS 2 1 Minimum number of numeric characters in new password.
+PASSWORD_MIN_SPECIAL_CHARS 2 0 Minimum number of special characters in new password.
+PASSWORD_MIN_AGE_DAYS 1 0 Period after a password is changed during which a password cannot be changed again, in days.
+PASSWORD_MAX_AGE_DAYS 30 90 Period after which password must be changed, in days.
+PASSWORD_MAX_RETRIES 3 5 Number of attempts users have to enter the correct password before their account is locked.
+PASSWORD_LOCKOUT_TIME_MINS 30 15 Period of time for which users will be locked after entering their password incorrectly many times (specified by MAX_RETRIES), in minutes.
+PASSWORD_HISTORY 5 0 Number of most recent passwords that may not be repeated by the user.
+
+statement error 2004
+CREATE PASSWORD POLICY default_policy PASSWORD_MIN_LENGTH = 1
+
+statement error 2004
+CREATE PASSWORD POLICY default_policy PASSWORD_MAX_LENGTH = 1000
+
+statement error 2004
+CREATE PASSWORD POLICY default_policy 
+    PASSWORD_MIN_LENGTH = 12
+    PASSWORD_MAX_LENGTH = 24
+    PASSWORD_MIN_UPPER_CASE_CHARS = 10
+    PASSWORD_MIN_LOWER_CASE_CHARS = 10
+    PASSWORD_MIN_NUMERIC_CHARS = 10
+    PASSWORD_MIN_SPECIAL_CHARS = 10
+
+statement ok
+CREATE PASSWORD POLICY default_policy COMMENT = 'default values'
+
+query TTT
+SHOW PASSWORD POLICIES
+----
+default_policy default values MIN_LENGTH=8, MAX_LENGTH=256, MIN_UPPER_CASE_CHARS=1, MIN_LOWER_CASE_CHARS=1, MIN_NUMERIC_CHARS=1, MIN_SPECIAL_CHARS=0, MIN_AGE_DAYS=0, MAX_AGE_DAYS=90, MAX_RETRIES=5, LOCKOUT_TIME_MINS=15, HISTORY=0
+test_policy test comment MIN_LENGTH=12, MAX_LENGTH=24, MIN_UPPER_CASE_CHARS=2, MIN_LOWER_CASE_CHARS=2, MIN_NUMERIC_CHARS=2, MIN_SPECIAL_CHARS=2, MIN_AGE_DAYS=1, MAX_AGE_DAYS=30, MAX_RETRIES=3, LOCKOUT_TIME_MINS=30, HISTORY=5
+
+statement ok
+ALTER PASSWORD POLICY default_policy SET 
+    PASSWORD_MIN_LENGTH = 10
+    PASSWORD_MAX_LENGTH = 25
+    PASSWORD_HISTORY = 8
+
+statement ok
+ALTER PASSWORD POLICY test_policy UNSET 
+    PASSWORD_MIN_LENGTH
+    PASSWORD_MAX_LENGTH
+    PASSWORD_LOCKOUT_TIME_MINS
+
+query TTT
+SHOW PASSWORD POLICIES
+----
+default_policy default values MIN_LENGTH=10, MAX_LENGTH=25, MIN_UPPER_CASE_CHARS=1, MIN_LOWER_CASE_CHARS=1, MIN_NUMERIC_CHARS=1, MIN_SPECIAL_CHARS=0, MIN_AGE_DAYS=0, MAX_AGE_DAYS=90, MAX_RETRIES=5, LOCKOUT_TIME_MINS=15, HISTORY=8
+test_policy test comment MIN_LENGTH=8, MAX_LENGTH=256, MIN_UPPER_CASE_CHARS=2, MIN_LOWER_CASE_CHARS=2, MIN_NUMERIC_CHARS=2, MIN_SPECIAL_CHARS=2, MIN_AGE_DAYS=1, MAX_AGE_DAYS=30, MAX_RETRIES=3, LOCKOUT_TIME_MINS=15, HISTORY=5
+
+query TTT
+SHOW PASSWORD POLICIES like 'test%'
+----
+test_policy test comment MIN_LENGTH=8, MAX_LENGTH=256, MIN_UPPER_CASE_CHARS=2, MIN_LOWER_CASE_CHARS=2, MIN_NUMERIC_CHARS=2, MIN_SPECIAL_CHARS=2, MIN_AGE_DAYS=1, MAX_AGE_DAYS=30, MAX_RETRIES=3, LOCKOUT_TIME_MINS=15, HISTORY=5
+
+query TTT
+SHOW PASSWORD POLICIES like 'default%'
+----
+default_policy default values MIN_LENGTH=10, MAX_LENGTH=25, MIN_UPPER_CASE_CHARS=1, MIN_LOWER_CASE_CHARS=1, MIN_NUMERIC_CHARS=1, MIN_SPECIAL_CHARS=0, MIN_AGE_DAYS=0, MAX_AGE_DAYS=90, MAX_RETRIES=5, LOCKOUT_TIME_MINS=15, HISTORY=8
+
+statement ok
+DROP USER IF EXISTS user1
+
+statement error 2211
+CREATE USER user1 IDENTIFIED BY '123456' WITH SET PASSWORD POLICY='test_policy2'
+
+statement error 2215
+CREATE USER user1 IDENTIFIED BY '123456' WITH SET PASSWORD POLICY='default_policy'
+
+statement error 2215
+CREATE USER user1 IDENTIFIED BY '123456abc' WITH SET PASSWORD POLICY='default_policy'
+
+statement ok
+CREATE USER user1 IDENTIFIED BY '123456abcD' WITH SET PASSWORD POLICY='default_policy'
+
+statement error 2215
+ALTER USER user1 IDENTIFIED BY '123456abcd'
+
+statement error 2215
+ALTER USER user1 IDENTIFIED BY '123456abcDE' WITH SET PASSWORD POLICY='test_policy'
+
+statement ok
+ALTER USER user1 IDENTIFIED BY '123456abcDE@!' WITH SET PASSWORD POLICY='test_policy'
+
+statement error 2214
+DROP PASSWORD POLICY test_policy
+
+statement ok
+ALTER USER user1 IDENTIFIED BY '123456' WITH UNSET PASSWORD POLICY
+
+statement ok
+DROP PASSWORD POLICY test_policy
+
+statement error 2211
+DROP PASSWORD POLICY test_policy
+
+statement error 2211
+DESC PASSWORD POLICY test_policy
+
+statement ok
+DROP PASSWORD POLICY default_policy