mirror of
https://github.com/lldap/lldap.git
synced 2026-06-18 09:05:20 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbc85d9594 | ||
|
|
e567d17062 | ||
|
|
d293941a44 | ||
|
|
203169fe4a | ||
|
|
3bf9ea5206 | ||
|
|
ddd6b469f2 | ||
|
|
36f10a9947 | ||
|
|
fc1731fad6 | ||
|
|
f949575a01 | ||
|
|
35d0d8005d | ||
|
|
92d1d83282 | ||
|
|
29b2411c6d | ||
|
|
e96e4c4adf | ||
|
|
2cd33c7215 | ||
|
|
dfe0773549 | ||
|
|
52a08d3ad9 | ||
|
|
2dc6178bd0 | ||
|
|
ed7484bffb | ||
|
|
68fc426ba3 | ||
|
|
a3d4eb04be | ||
|
|
dc883a060a | ||
|
|
82b16a3716 |
2
.github/workflows/Dockerfile.ci.alpine-base
vendored
2
.github/workflows/Dockerfile.ci.alpine-base
vendored
@@ -59,7 +59,7 @@ RUN set -x \
|
||||
&& for file in $(cat /lldap/app/static/fonts/fonts.txt); do wget -P app/static/fonts "$file"; done \
|
||||
&& chmod a+r -R .
|
||||
|
||||
FROM alpine:3.19
|
||||
FROM alpine:3.22
|
||||
WORKDIR /app
|
||||
ENV UID=1000
|
||||
ENV GID=1000
|
||||
|
||||
2
.github/workflows/Dockerfile.dev
vendored
2
.github/workflows/Dockerfile.dev
vendored
@@ -1,5 +1,5 @@
|
||||
# Keep tracking base image
|
||||
FROM rust:1.89-slim-bookworm
|
||||
FROM rust:1.91-slim-bookworm
|
||||
|
||||
# Set needed env path
|
||||
ENV PATH="/opt/armv7l-linux-musleabihf-cross/:/opt/armv7l-linux-musleabihf-cross/bin/:/opt/aarch64-linux-musl-cross/:/opt/aarch64-linux-musl-cross/bin/:/opt/x86_64-linux-musl-cross/:/opt/x86_64-linux-musl-cross/bin/:$PATH"
|
||||
|
||||
2
.github/workflows/docker-build-static.yml
vendored
2
.github/workflows/docker-build-static.yml
vendored
@@ -24,7 +24,7 @@ on:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
MSRV: "1.89.0"
|
||||
MSRV: "1.91.0"
|
||||
|
||||
### CI Docs
|
||||
|
||||
|
||||
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
MSRV: "1.89.0"
|
||||
MSRV: "1.91.0"
|
||||
|
||||
jobs:
|
||||
pre_job:
|
||||
|
||||
@@ -34,7 +34,7 @@ Small release, focused on LDAP compatibility, TLS maintenance, dependency upgrad
|
||||
- Updated the LDAP dependency stack, including `ldap3_proto`, in response to
|
||||
security advisory
|
||||
[`GHSA-qcxq-75wr-5cm8`](https://github.com/kanidm/ldap3/security/advisories/GHSA-qcxq-75wr-5cm8),
|
||||
where a specially crafted LDAP query could make the server use unbounded RAM
|
||||
where a specially crafted LDAP query could make the server crash
|
||||
|
||||
### Cleanups
|
||||
|
||||
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2743,6 +2743,7 @@ dependencies = [
|
||||
"lldap_auth",
|
||||
"lldap_frontend_options",
|
||||
"lldap_validation",
|
||||
"percent-encoding",
|
||||
"rand 0.8.6",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -16,7 +16,7 @@ edition = "2024"
|
||||
homepage = "https://github.com/lldap/lldap"
|
||||
license = "GPL-3.0-only"
|
||||
repository = "https://github.com/lldap/lldap"
|
||||
rust-version = "1.89.0"
|
||||
rust-version = "1.91.0"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
@@ -24,6 +24,7 @@ rand = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
percent-encoding = "2"
|
||||
url-escape = "0.1.1"
|
||||
validator = "0.14"
|
||||
validator_derive = "0.14"
|
||||
|
||||
@@ -183,6 +183,16 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
/// Percent-decode a URL path segment into a user ID string.
|
||||
/// Returns `None` if the decoded bytes are not valid UTF-8, so the caller
|
||||
/// can redirect to a safe page rather than silently mangling the ID.
|
||||
fn decode_user_id(raw: &str) -> Option<String> {
|
||||
percent_encoding::percent_decode_str(raw)
|
||||
.decode_utf8()
|
||||
.ok()
|
||||
.map(|s| s.into_owned())
|
||||
}
|
||||
|
||||
fn dispatch_route(
|
||||
switch: &AppRoute,
|
||||
link: &Scope<Self>,
|
||||
@@ -248,11 +258,17 @@ impl App {
|
||||
AppRoute::GroupDetails { group_id } => html! {
|
||||
<GroupDetails group_id={*group_id} is_admin={is_admin} />
|
||||
},
|
||||
AppRoute::UserDetails { user_id } => html! {
|
||||
<UserDetails username={user_id.clone()} is_admin={is_admin} />
|
||||
AppRoute::UserDetails { user_id } => match Self::decode_user_id(user_id) {
|
||||
Some(decoded_id) => html! {
|
||||
<UserDetails username={decoded_id} is_admin={is_admin} />
|
||||
},
|
||||
None => html! { <Redirect to={AppRoute::Login} /> },
|
||||
},
|
||||
AppRoute::ChangePassword { user_id } => html! {
|
||||
<ChangePasswordForm username={user_id.clone()} is_admin={is_admin} />
|
||||
AppRoute::ChangePassword { user_id } => match Self::decode_user_id(user_id) {
|
||||
Some(decoded_id) => html! {
|
||||
<ChangePasswordForm username={decoded_id} is_admin={is_admin} />
|
||||
},
|
||||
None => html! { <Redirect to={AppRoute::Login} /> },
|
||||
},
|
||||
AppRoute::StartResetPassword => match password_reset_enabled {
|
||||
Some(true) => html! { <ResetPasswordStep1Form /> },
|
||||
|
||||
@@ -173,22 +173,31 @@ fn get_group_attribute_equality_filter(
|
||||
typ: AttributeType,
|
||||
is_list: bool,
|
||||
value: &str,
|
||||
) -> GroupRequestFilter {
|
||||
) -> LdapResult<GroupRequestFilter> {
|
||||
if is_list {
|
||||
return Err(LdapError {
|
||||
code: LdapResultCode::UnwillingToPerform,
|
||||
message: format!(
|
||||
"Equality filter on list attribute \"{}\" is not supported",
|
||||
field
|
||||
),
|
||||
});
|
||||
}
|
||||
let value_lc = value.to_ascii_lowercase();
|
||||
let serialized_value = deserialize_attribute_value(&[value.to_owned()], typ, is_list);
|
||||
let serialized_value_lc = deserialize_attribute_value(&[value_lc.to_owned()], typ, is_list);
|
||||
let serialized_value = deserialize_attribute_value(&[value.to_owned()], typ, false);
|
||||
let serialized_value_lc = deserialize_attribute_value(&[value_lc.to_owned()], typ, false);
|
||||
match (serialized_value, serialized_value_lc) {
|
||||
(Ok(v), Ok(v_lc)) => GroupRequestFilter::Or(vec![
|
||||
(Ok(v), Ok(v_lc)) => Ok(GroupRequestFilter::Or(vec![
|
||||
GroupRequestFilter::AttributeEquality(field.clone(), v),
|
||||
GroupRequestFilter::AttributeEquality(field.clone(), v_lc),
|
||||
]),
|
||||
])),
|
||||
(Ok(_), Err(e)) => {
|
||||
warn!("Invalid value for attribute {} (lowercased): {}", field, e);
|
||||
GroupRequestFilter::False
|
||||
Ok(GroupRequestFilter::False)
|
||||
}
|
||||
(Err(e), _) => {
|
||||
warn!("Invalid value for attribute {}: {}", field, e);
|
||||
GroupRequestFilter::False
|
||||
Ok(GroupRequestFilter::False)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -259,9 +268,9 @@ fn convert_group_filter(
|
||||
}
|
||||
Ok(GroupRequestFilter::False)
|
||||
}
|
||||
GroupFieldType::Attribute(field, typ, is_list) => Ok(
|
||||
get_group_attribute_equality_filter(&field, typ, is_list, value),
|
||||
),
|
||||
GroupFieldType::Attribute(field, typ, is_list) => {
|
||||
get_group_attribute_equality_filter(&field, typ, is_list, value)
|
||||
}
|
||||
GroupFieldType::CreationDate => Err(LdapError {
|
||||
code: LdapResultCode::UnwillingToPerform,
|
||||
message: "Creation date filter for groups not supported".to_owned(),
|
||||
@@ -671,6 +680,82 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_equality_filter_on_list_group_attribute_returns_error() {
|
||||
use lldap_domain::schema::{AttributeList, AttributeSchema, Schema};
|
||||
let mut mock = MockTestBackendHandler::new();
|
||||
mock.expect_get_schema().returning(|| {
|
||||
Ok(Schema {
|
||||
user_attributes: AttributeList {
|
||||
attributes: Vec::new(),
|
||||
},
|
||||
group_attributes: AttributeList {
|
||||
attributes: vec![AttributeSchema {
|
||||
name: "tags".into(),
|
||||
attribute_type: AttributeType::String,
|
||||
is_list: true,
|
||||
is_visible: true,
|
||||
is_editable: true,
|
||||
is_hardcoded: false,
|
||||
is_readonly: false,
|
||||
}],
|
||||
},
|
||||
extra_user_object_classes: Vec::new(),
|
||||
extra_group_object_classes: Vec::new(),
|
||||
})
|
||||
});
|
||||
let ldap_handler = setup_bound_admin_handler(mock).await;
|
||||
let request = make_group_search_request(
|
||||
LdapFilter::Equality("tags".to_string(), "foo".to_string()),
|
||||
vec!["dn"],
|
||||
);
|
||||
assert_eq!(
|
||||
ldap_handler.do_search_or_dse(&request).await,
|
||||
Err(LdapError {
|
||||
code: LdapResultCode::UnwillingToPerform,
|
||||
message: r#"Equality filter on list attribute "tags" is not supported"#.to_string(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_equality_filter_on_non_list_group_attribute() {
|
||||
use lldap_domain::schema::{AttributeList, AttributeSchema, Schema};
|
||||
let mut mock = MockTestBackendHandler::new();
|
||||
mock.expect_get_schema().returning(|| {
|
||||
Ok(Schema {
|
||||
user_attributes: AttributeList {
|
||||
attributes: Vec::new(),
|
||||
},
|
||||
group_attributes: AttributeList {
|
||||
attributes: vec![AttributeSchema {
|
||||
name: "club_name".into(),
|
||||
attribute_type: AttributeType::String,
|
||||
is_list: false,
|
||||
is_visible: true,
|
||||
is_editable: true,
|
||||
is_hardcoded: false,
|
||||
is_readonly: false,
|
||||
}],
|
||||
},
|
||||
extra_user_object_classes: Vec::new(),
|
||||
extra_group_object_classes: Vec::new(),
|
||||
})
|
||||
});
|
||||
mock.expect_list_groups()
|
||||
.times(1)
|
||||
.return_once(|_| Ok(vec![]));
|
||||
let ldap_handler = setup_bound_admin_handler(mock).await;
|
||||
let request = make_group_search_request(
|
||||
LdapFilter::Equality("club_name".to_string(), "myclub".to_string()),
|
||||
vec!["dn"],
|
||||
);
|
||||
assert_eq!(
|
||||
ldap_handler.do_search_or_dse(&request).await,
|
||||
Ok(vec![make_search_success()])
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_search_group_as_scope() {
|
||||
let mut mock = MockTestBackendHandler::new();
|
||||
|
||||
@@ -182,22 +182,31 @@ fn get_user_attribute_equality_filter(
|
||||
typ: AttributeType,
|
||||
is_list: bool,
|
||||
value: &str,
|
||||
) -> UserRequestFilter {
|
||||
) -> LdapResult<UserRequestFilter> {
|
||||
if is_list {
|
||||
return Err(LdapError {
|
||||
code: LdapResultCode::UnwillingToPerform,
|
||||
message: format!(
|
||||
"Equality filter on list attribute \"{}\" is not supported",
|
||||
field
|
||||
),
|
||||
});
|
||||
}
|
||||
let value_lc = value.to_ascii_lowercase();
|
||||
let serialized_value = deserialize_attribute_value(&[value.to_owned()], typ, is_list);
|
||||
let serialized_value_lc = deserialize_attribute_value(&[value_lc.to_owned()], typ, is_list);
|
||||
let serialized_value = deserialize_attribute_value(&[value.to_owned()], typ, false);
|
||||
let serialized_value_lc = deserialize_attribute_value(&[value_lc.to_owned()], typ, false);
|
||||
match (serialized_value, serialized_value_lc) {
|
||||
(Ok(v), Ok(v_lc)) => UserRequestFilter::Or(vec![
|
||||
(Ok(v), Ok(v_lc)) => Ok(UserRequestFilter::Or(vec![
|
||||
UserRequestFilter::AttributeEquality(field.clone(), v),
|
||||
UserRequestFilter::AttributeEquality(field.clone(), v_lc),
|
||||
]),
|
||||
])),
|
||||
(Ok(_), Err(e)) => {
|
||||
warn!("Invalid value for attribute {} (lowercased): {}", field, e);
|
||||
UserRequestFilter::False
|
||||
Ok(UserRequestFilter::False)
|
||||
}
|
||||
(Err(e), _) => {
|
||||
warn!("Invalid value for attribute {}: {}", field, e);
|
||||
UserRequestFilter::False
|
||||
Ok(UserRequestFilter::False)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,9 +288,9 @@ fn convert_user_filter(
|
||||
UserFieldType::PrimaryField(field) => {
|
||||
Ok(UserRequestFilter::Equality(field, value_lc))
|
||||
}
|
||||
UserFieldType::Attribute(field, typ, is_list) => Ok(
|
||||
get_user_attribute_equality_filter(&field, typ, is_list, value),
|
||||
),
|
||||
UserFieldType::Attribute(field, typ, is_list) => {
|
||||
get_user_attribute_equality_filter(&field, typ, is_list, value)
|
||||
}
|
||||
UserFieldType::NoMatch => {
|
||||
if !ldap_info.ignored_user_attributes.contains(&field) {
|
||||
warn!(
|
||||
@@ -786,6 +795,83 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_equality_filter_on_list_user_attribute_returns_error() {
|
||||
use lldap_domain::schema::{AttributeList, AttributeSchema, Schema};
|
||||
let mut mock = MockTestBackendHandler::new();
|
||||
mock.expect_get_schema().returning(|| {
|
||||
Ok(Schema {
|
||||
user_attributes: AttributeList {
|
||||
attributes: vec![AttributeSchema {
|
||||
name: "mailalias".into(),
|
||||
attribute_type: AttributeType::String,
|
||||
is_list: true,
|
||||
is_visible: true,
|
||||
is_editable: true,
|
||||
is_hardcoded: false,
|
||||
is_readonly: false,
|
||||
}],
|
||||
},
|
||||
group_attributes: AttributeList {
|
||||
attributes: Vec::new(),
|
||||
},
|
||||
extra_user_object_classes: Vec::new(),
|
||||
extra_group_object_classes: Vec::new(),
|
||||
})
|
||||
});
|
||||
let ldap_handler = setup_bound_admin_handler(mock).await;
|
||||
let request = make_user_search_request(
|
||||
LdapFilter::Equality("mailalias".to_string(), "alias@example.com".to_string()),
|
||||
vec!["dn"],
|
||||
);
|
||||
assert_eq!(
|
||||
ldap_handler.do_search_or_dse(&request).await,
|
||||
Err(LdapError {
|
||||
code: LdapResultCode::UnwillingToPerform,
|
||||
message: r#"Equality filter on list attribute "mailalias" is not supported"#
|
||||
.to_string(),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_equality_filter_on_non_list_user_attribute() {
|
||||
use lldap_domain::schema::{AttributeList, AttributeSchema, Schema};
|
||||
let mut mock = MockTestBackendHandler::new();
|
||||
mock.expect_get_schema().returning(|| {
|
||||
Ok(Schema {
|
||||
user_attributes: AttributeList {
|
||||
attributes: vec![AttributeSchema {
|
||||
name: "nickname".into(),
|
||||
attribute_type: AttributeType::String,
|
||||
is_list: false,
|
||||
is_visible: true,
|
||||
is_editable: true,
|
||||
is_hardcoded: false,
|
||||
is_readonly: false,
|
||||
}],
|
||||
},
|
||||
group_attributes: AttributeList {
|
||||
attributes: Vec::new(),
|
||||
},
|
||||
extra_user_object_classes: Vec::new(),
|
||||
extra_group_object_classes: Vec::new(),
|
||||
})
|
||||
});
|
||||
mock.expect_list_users()
|
||||
.times(1)
|
||||
.return_once(|_, _| Ok(vec![]));
|
||||
let ldap_handler = setup_bound_admin_handler(mock).await;
|
||||
let request = make_user_search_request(
|
||||
LdapFilter::Equality("nickname".to_string(), "alice".to_string()),
|
||||
vec!["dn"],
|
||||
);
|
||||
assert_eq!(
|
||||
ldap_handler.do_search_or_dse(&request).await,
|
||||
Ok(vec![make_search_success()])
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_search_cn_case_insensitive() {
|
||||
use lldap_domain::uuid;
|
||||
|
||||
@@ -29,28 +29,76 @@ impl ReadSchemaBackendHandler for SqlBackendHandler {
|
||||
#[async_trait]
|
||||
impl SchemaBackendHandler for SqlBackendHandler {
|
||||
async fn add_user_attribute(&self, request: CreateAttributeRequest) -> Result<()> {
|
||||
let new_attribute = model::user_attribute_schema::ActiveModel {
|
||||
attribute_name: Set(request.name),
|
||||
attribute_type: Set(request.attribute_type),
|
||||
is_list: Set(request.is_list),
|
||||
is_user_visible: Set(request.is_visible),
|
||||
is_user_editable: Set(request.is_editable),
|
||||
is_hardcoded: Set(false),
|
||||
};
|
||||
new_attribute.insert(&self.sql_pool).await?;
|
||||
self.sql_pool
|
||||
.transaction::<_, (), DomainError>(|transaction| {
|
||||
Box::pin(async move {
|
||||
// Check for conflicting group attribute with the same name but different type.
|
||||
// Done inside the transaction to prevent TOCTOU races.
|
||||
let schema = Self::get_schema_with_transaction(transaction).await?;
|
||||
if let Some(group_attr) = schema
|
||||
.group_attributes
|
||||
.attributes
|
||||
.iter()
|
||||
.find(|a| a.name == request.name)
|
||||
&& group_attr.attribute_type != request.attribute_type
|
||||
{
|
||||
return Err(DomainError::InternalError(format!(
|
||||
"A group attribute '{}' already exists with type {:?}, \
|
||||
cannot create a user attribute with type {:?}. \
|
||||
LDAP requires each attribute name to have a single type.",
|
||||
request.name, group_attr.attribute_type, request.attribute_type
|
||||
)));
|
||||
}
|
||||
let new_attribute = model::user_attribute_schema::ActiveModel {
|
||||
attribute_name: Set(request.name),
|
||||
attribute_type: Set(request.attribute_type),
|
||||
is_list: Set(request.is_list),
|
||||
is_user_visible: Set(request.is_visible),
|
||||
is_user_editable: Set(request.is_editable),
|
||||
is_hardcoded: Set(false),
|
||||
};
|
||||
new_attribute.insert(transaction).await?;
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn add_group_attribute(&self, request: CreateAttributeRequest) -> Result<()> {
|
||||
let new_attribute = model::group_attribute_schema::ActiveModel {
|
||||
attribute_name: Set(request.name),
|
||||
attribute_type: Set(request.attribute_type),
|
||||
is_list: Set(request.is_list),
|
||||
is_group_visible: Set(request.is_visible),
|
||||
is_group_editable: Set(request.is_editable),
|
||||
is_hardcoded: Set(false),
|
||||
};
|
||||
new_attribute.insert(&self.sql_pool).await?;
|
||||
self.sql_pool
|
||||
.transaction::<_, (), DomainError>(|transaction| {
|
||||
Box::pin(async move {
|
||||
// Check for conflicting user attribute with the same name but different type.
|
||||
// Done inside the transaction to prevent TOCTOU races.
|
||||
let schema = Self::get_schema_with_transaction(transaction).await?;
|
||||
if let Some(user_attr) = schema
|
||||
.user_attributes
|
||||
.attributes
|
||||
.iter()
|
||||
.find(|a| a.name == request.name)
|
||||
&& user_attr.attribute_type != request.attribute_type
|
||||
{
|
||||
return Err(DomainError::InternalError(format!(
|
||||
"A user attribute '{}' already exists with type {:?}, \
|
||||
cannot create a group attribute with type {:?}. \
|
||||
LDAP requires each attribute name to have a single type.",
|
||||
request.name, user_attr.attribute_type, request.attribute_type
|
||||
)));
|
||||
}
|
||||
let new_attribute = model::group_attribute_schema::ActiveModel {
|
||||
attribute_name: Set(request.name),
|
||||
attribute_type: Set(request.attribute_type),
|
||||
is_list: Set(request.is_list),
|
||||
is_group_visible: Set(request.is_visible),
|
||||
is_group_editable: Set(request.is_editable),
|
||||
is_hardcoded: Set(false),
|
||||
};
|
||||
new_attribute.insert(transaction).await?;
|
||||
Ok(())
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ services:
|
||||
# - LLDAP_SMTP_OPTIONS__PASSWORD=PasswordGoesHere # The SMTP password
|
||||
# - LLDAP_SMTP_OPTIONS__FROM=no-reply <no-reply@example.com> # The header field, optional: how the sender appears in the email. The first is a free-form name, followed by an email between <>.
|
||||
# - LLDAP_SMTP_OPTIONS__TO=admin <admin@example.com> # Same for reply-to, optional.
|
||||
# - LLDAP_HTTP_URL=https://lldap.example.com # URL used in the email template to compose the reset link
|
||||
```
|
||||
|
||||
Then the service will listen on two ports, one for LDAP and one for the web
|
||||
|
||||
@@ -19,11 +19,13 @@ configuration files:
|
||||
- [Dolibarr](dolibarr.md)
|
||||
- [Duo Auth Proxy](duo_auth_proxy.md)
|
||||
- [Ejabberd](ejabberd.md)
|
||||
- [Elasticsearch](elasticsearch.md)
|
||||
- [Emby](emby.md)
|
||||
- [Ergo IRCd](ergo.md)
|
||||
- [Gerrit](gerrit.md)
|
||||
- [Gitea](gitea.md)
|
||||
- [GitLab](gitlab.md)
|
||||
- [Gogs](gogs.md)
|
||||
- [Grafana](grafana_ldap_config.toml)
|
||||
- [Grocy](grocy.md)
|
||||
- [Harbor](harbor.md)
|
||||
|
||||
56
example_configs/elasticsearch.md
Normal file
56
example_configs/elasticsearch.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Elasticsearch configuration
|
||||
|
||||
> ⚠️ Configuring Elasticsearch to use LDAP auth requires a paid licence. Only the `default` and `file` realms are enabled on a Basic licence. Using the trial licence on cluster init will allow LDAP auth to be configured.
|
||||
|
||||
This basic configuration example is for LLDAP auth on [Elastic Cloud on Kubernetes (ECK)](https://www.elastic.co/docs/deploy-manage/deploy/cloud-on-k8s). Advanced configuration can be found in the [Elastic docs](https://www.elastic.co/docs/deploy-manage/users-roles/cluster-or-deployment-auth/ldap).
|
||||
|
||||
## Elasticsearch
|
||||
|
||||
To perform auth using LLDAP in Elasticsearch, add the following lines to the Elasticsearch spec:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
nodesets:
|
||||
- name: elasticsearch
|
||||
config:
|
||||
xpack.security.authc.realms.ldap:
|
||||
ldap1:
|
||||
order: 1
|
||||
enabled: true
|
||||
url: "ldap://<ip.of.lldap.instance>:3890"
|
||||
user_dn_templates:
|
||||
- "uid={0},ou=people,dc=example,dc=com"
|
||||
bind_dn: "uid=admin,ou=people,dc=example,dc=com"
|
||||
group_search:
|
||||
base_dn: "ou=groups,dc=example,dc=com"
|
||||
unmapped_groups_as_roles: false
|
||||
secureSettings:
|
||||
- secretName: elasticsearch-keystore-values
|
||||
```
|
||||
|
||||
Then, create a secret called `elasticsearch-keystore-values`:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
type: Opaque
|
||||
metadata:
|
||||
name: elasticsearch-keystore-values
|
||||
namespace: elastic
|
||||
data:
|
||||
xpack.security.authc.realms.ldap.ldap1.secure_bind_password: base64_encoded_ldap_admin_password
|
||||
```
|
||||
|
||||
## Kibana
|
||||
|
||||
To allow Kibana to auth logins using LLDAP, add the following lines to the Kibana spec:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
config:
|
||||
xpack.security.authc.providers:
|
||||
basic.ldap1:
|
||||
order: 0
|
||||
```
|
||||
|
||||
Unless doing additional manifest configuration to automatically map, you will need to create a role mapping between an LLDAP role and an Elasticsearch role (e.g. `superuser`). This can be done by logging in using the default `elastic` user created during cluster init and then creating a role mapping in Stack Management. Once created, you will be able to login using LLDAP auth.
|
||||
@@ -1,6 +1,21 @@
|
||||
# Gogs LDAP configuration
|
||||
|
||||
Gogs can make use of LDAP and therefore lldap.
|
||||
## Via Simple Auth (easier)
|
||||
|
||||
Go to the Administration settings, then go to Authentication. There, you have to add an authentication source.
|
||||
|
||||
For type, select "LDAP (Simple Auth)".
|
||||
Name your authentication source however you'd like.
|
||||
It is up to you to select your security protocol, but the only two compatible options are "LDAPS" and "Unencrypted".
|
||||
As your host, put in the IP or FQDN (if you have DNS).
|
||||
As your port, check your configuration. It will generally be 3890 for unencrypted (once again check your config/docker compose files), and 6360 for LDAPS (once again check your config/docker compose files).
|
||||
Your User DN should follow this pattern: `uid=%s,ou=people,<your_base_dn>` (for example, `uid=%s,ou=people,dc=example,dc=com`). Replace `<your_base_dn>` with your actual base DN.
|
||||
It is recommended to have your user filter to be `(&(objectClass=person)(uid=%s))`.
|
||||
Set username attribute to `uid`, Given Name to `givenName`, surname to `sn`, and email to `mail`
|
||||
|
||||
You can (and should if you don't know LDAP) leave the rest empty.
|
||||
|
||||
## Via Bind DN (more complicated)
|
||||
|
||||
The following configuration is adapted from the example configuration at [their repository](https://github.com/gogs/gogs/blob/main/conf/auth.d/ldap_bind_dn.conf.example).
|
||||
The example is a container configuration - the file should live within `conf/auth.d/some_name.conf`:
|
||||
|
||||
@@ -30,7 +30,7 @@ Unique Identifier Attribute - entryUUID
|
||||
- Group Object Filter - objectClass=groupOfUniqueNames
|
||||
|
||||
Member Attribute
|
||||
member
|
||||
uniqueMember
|
||||
|
||||
Validate Attribute
|
||||
enter a user e-mail address who has been added in LLDAP , and click test configuration, test show be successful
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
};
|
||||
|
||||
# MSRV from the project
|
||||
rustVersion = "1.89.0";
|
||||
rustVersion = "1.91.0";
|
||||
|
||||
# Rust toolchain with required components
|
||||
rustToolchain = pkgs.rust-bin.stable.${rustVersion}.default.override {
|
||||
@@ -159,4 +159,4 @@
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "1.89.0"
|
||||
channel = "1.91.0"
|
||||
|
||||
@@ -712,9 +712,9 @@ main() {
|
||||
redundant_users="$(printf '%s' "$redundant_users" | jq --compact-output --arg id "$id" '. - [$id]')"
|
||||
|
||||
if [[ "$password_file" != 'null' ]] && [[ "$password_file" != '""' ]]; then
|
||||
"$LLDAP_SET_PASSWORD_PATH" --base-url "$LLDAP_URL" --token "$TOKEN" --username "$id" --password "$(cat $password_file)"
|
||||
LLDAP_USER_PASSWORD="$(< "$password_file")" "$LLDAP_SET_PASSWORD_PATH" --base-url "$LLDAP_URL" --token "$TOKEN" --username "$id"
|
||||
elif [[ "$password" != 'null' ]] && [[ "$password" != '""' ]]; then
|
||||
"$LLDAP_SET_PASSWORD_PATH" --base-url "$LLDAP_URL" --token "$TOKEN" --username "$id" --password "$password"
|
||||
LLDAP_USER_PASSWORD="$password" "$LLDAP_SET_PASSWORD_PATH" --base-url "$LLDAP_URL" --token "$TOKEN" --username "$id"
|
||||
fi
|
||||
|
||||
# Process custom attributes
|
||||
|
||||
@@ -135,7 +135,7 @@ pub struct RunOpts {
|
||||
#[clap(long, env = "LLDAP_SERVER_KEY_SEED")]
|
||||
pub server_key_seed: Option<String>,
|
||||
|
||||
/// Change ldap host. Default: "0.0.0.0"
|
||||
/// Change ldap host. Default: "::"
|
||||
#[clap(long, env = "LLDAP_LDAP_HOST")]
|
||||
pub ldap_host: Option<String>,
|
||||
|
||||
@@ -143,7 +143,7 @@ pub struct RunOpts {
|
||||
#[clap(long, env = "LLDAP_LDAP_PORT")]
|
||||
pub ldap_port: Option<u16>,
|
||||
|
||||
/// Change HTTP API host. Default: "0.0.0.0"
|
||||
/// Change HTTP API host. Default: "::"
|
||||
#[clap(long, env = "LLDAP_HTTP_HOST")]
|
||||
pub http_host: Option<String>,
|
||||
|
||||
@@ -151,7 +151,7 @@ pub struct RunOpts {
|
||||
#[clap(long, env = "LLDAP_HTTP_PORT")]
|
||||
pub http_port: Option<u16>,
|
||||
|
||||
/// URL of the server, for password reset links.
|
||||
/// URL of the server, for password reset links. Default: "http://localhost"
|
||||
#[clap(long, env = "LLDAP_HTTP_URL")]
|
||||
pub http_url: Option<Url>,
|
||||
|
||||
|
||||
@@ -105,11 +105,11 @@ pub struct HttpUrl(pub Url);
|
||||
#[derive(Clone, Deserialize, Serialize, derive_builder::Builder, derive_more::Debug)]
|
||||
#[builder(pattern = "owned", build_fn(name = "private_build"))]
|
||||
pub struct Configuration {
|
||||
#[builder(default = r#"String::from("0.0.0.0")"#)]
|
||||
#[builder(default = r#"String::from("::")"#)]
|
||||
pub ldap_host: String,
|
||||
#[builder(default = "3890")]
|
||||
pub ldap_port: u16,
|
||||
#[builder(default = r#"String::from("0.0.0.0")"#)]
|
||||
#[builder(default = r#"String::from("::")"#)]
|
||||
pub http_host: String,
|
||||
#[builder(default = "17170")]
|
||||
pub http_port: u16,
|
||||
|
||||
Reference in New Issue
Block a user