mirror of
https://codeberg.org/icewind/haze.git
synced 2026-10-01 08:44:09 +02:00
Merge pull request 'feat(service): Add OIDC service' (#47) from artonge/haze:artonge/feat/oidc into main
Reviewed-on: https://codeberg.org/icewind/haze/pulls/47
This commit is contained in:
commit
b38cbbe7ba
6 changed files with 200 additions and 16 deletions
|
|
@ -83,6 +83,7 @@ Additionally, you can use the following options when starting an instance:
|
||||||
- `s3m`: enable multi-instance S3 setup.
|
- `s3m`: enable multi-instance S3 setup.
|
||||||
- `ldap`: set up an LDAP server.
|
- `ldap`: set up an LDAP server.
|
||||||
- `saml`: set up authentik as a SAML IDP.
|
- `saml`: set up authentik as a SAML IDP.
|
||||||
|
- `oidc`: set up authentik as an OIDC IDP.
|
||||||
- `office`: set up a Nextcloud Office server.
|
- `office`: set up a Nextcloud Office server.
|
||||||
- `onlyoffice` setup an onlyoffice document server.
|
- `onlyoffice` setup an onlyoffice document server.
|
||||||
- `push` set up [client push](https://github.com/nextcloud/notify_push).
|
- `push` set up [client push](https://github.com/nextcloud/notify_push).
|
||||||
|
|
|
||||||
71
blueprints/authentik-oidc.yaml
Normal file
71
blueprints/authentik-oidc.yaml
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
# yaml-language-server: $schema=https://goauthentik.io/blueprints/schema.json
|
||||||
|
version: 1
|
||||||
|
metadata:
|
||||||
|
name: haze-nextcloud-oidc
|
||||||
|
entries:
|
||||||
|
- model: authentik_crypto.certificatekeypair
|
||||||
|
id: haze-oidc-cert
|
||||||
|
identifiers:
|
||||||
|
name: haze-oidc
|
||||||
|
attrs:
|
||||||
|
certificate_data: !File /haze/authentik/public.crt
|
||||||
|
key_data: !File /haze/authentik/private.key
|
||||||
|
|
||||||
|
# Prefix OIDC users with 'oidc-' so that they don't collide with local users.
|
||||||
|
- model: authentik_providers_oauth2.scopemapping
|
||||||
|
id: haze-nextcloud-oidc-uid
|
||||||
|
identifiers:
|
||||||
|
name: haze-nextcloud-oidc-uid
|
||||||
|
attrs:
|
||||||
|
scope_name: nextcloud
|
||||||
|
expression: 'return {"nextcloud_uid": "oidc-" + request.user.username}'
|
||||||
|
|
||||||
|
- model: authentik_providers_oauth2.oauth2provider
|
||||||
|
id: nextcloud-oidc-provider
|
||||||
|
identifiers:
|
||||||
|
name: nextcloud-oidc
|
||||||
|
attrs:
|
||||||
|
client_type: confidential
|
||||||
|
client_id: nextcloud
|
||||||
|
client_secret: haze-oidc-secret
|
||||||
|
grant_types:
|
||||||
|
- authorization_code
|
||||||
|
- refresh_token
|
||||||
|
redirect_uris:
|
||||||
|
- matching_mode: strict
|
||||||
|
url:
|
||||||
|
!Format [
|
||||||
|
"%s/index.php/apps/user_oidc/code",
|
||||||
|
!File /haze/authentik/nextcloud-url,
|
||||||
|
]
|
||||||
|
signing_key: !KeyOf haze-oidc-cert
|
||||||
|
authorization_flow:
|
||||||
|
!Find [
|
||||||
|
authentik_flows.flow,
|
||||||
|
[slug, default-provider-authorization-implicit-consent],
|
||||||
|
]
|
||||||
|
invalidation_flow:
|
||||||
|
!Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||||
|
property_mappings:
|
||||||
|
- !KeyOf haze-nextcloud-oidc-uid
|
||||||
|
- !Find [
|
||||||
|
authentik_providers_oauth2.scopemapping,
|
||||||
|
[managed, goauthentik.io/providers/oauth2/scope-openid],
|
||||||
|
]
|
||||||
|
- !Find [
|
||||||
|
authentik_providers_oauth2.scopemapping,
|
||||||
|
[managed, goauthentik.io/providers/oauth2/scope-email],
|
||||||
|
]
|
||||||
|
- !Find [
|
||||||
|
authentik_providers_oauth2.scopemapping,
|
||||||
|
[managed, goauthentik.io/providers/oauth2/scope-profile],
|
||||||
|
]
|
||||||
|
|
||||||
|
- model: authentik_core.application
|
||||||
|
identifiers:
|
||||||
|
slug: nextcloud-oidc
|
||||||
|
attrs:
|
||||||
|
name: Nextcloud with OIDC
|
||||||
|
provider: !KeyOf nextcloud-oidc-provider
|
||||||
|
meta_launch_url: !File /haze/authentik/nextcloud-url
|
||||||
|
meta_description: Nextcloud instance provisioned by haze
|
||||||
|
|
@ -19,7 +19,7 @@ mod webhook;
|
||||||
|
|
||||||
use crate::cloud::CloudOptions;
|
use crate::cloud::CloudOptions;
|
||||||
use crate::config::{HazeConfig, Preset, ProxyConfig};
|
use crate::config::{HazeConfig, Preset, ProxyConfig};
|
||||||
pub use crate::service::authentik::{Authentik, AuthentikSaml};
|
pub use crate::service::authentik::{Authentik, AuthentikOidc, AuthentikSaml};
|
||||||
pub use crate::service::clam::{Clam, ClamIcap, ClamIcapTls, ClamSocket};
|
pub use crate::service::clam::{Clam, ClamIcap, ClamIcapTls, ClamSocket};
|
||||||
use crate::service::dav::Dav;
|
use crate::service::dav::Dav;
|
||||||
use crate::service::imaginary::Imaginary;
|
use crate::service::imaginary::Imaginary;
|
||||||
|
|
@ -320,6 +320,8 @@ pub enum ServiceType {
|
||||||
Authentik,
|
Authentik,
|
||||||
/// Configure Authentik as a SAML IDP for Nextcloud
|
/// Configure Authentik as a SAML IDP for Nextcloud
|
||||||
Saml,
|
Saml,
|
||||||
|
/// Configure Authentik as an OIDC IDP for Nextcloud
|
||||||
|
Oidc,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[enum_dispatch]
|
#[enum_dispatch]
|
||||||
|
|
@ -355,6 +357,7 @@ pub enum Service {
|
||||||
Webhook(Webhook),
|
Webhook(Webhook),
|
||||||
Authentik(Authentik),
|
Authentik(Authentik),
|
||||||
AuthentikSaml(AuthentikSaml),
|
AuthentikSaml(AuthentikSaml),
|
||||||
|
AuthentikOidc(AuthentikOidc),
|
||||||
Preset(PresetService),
|
Preset(PresetService),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -406,6 +409,10 @@ impl Service {
|
||||||
Service::Authentik(Authentik),
|
Service::Authentik(Authentik),
|
||||||
Service::AuthentikSaml(AuthentikSaml),
|
Service::AuthentikSaml(AuthentikSaml),
|
||||||
]),
|
]),
|
||||||
|
ServiceType::Oidc => Some(vec![
|
||||||
|
Service::Authentik(Authentik),
|
||||||
|
Service::AuthentikOidc(AuthentikOidc),
|
||||||
|
]),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
presets
|
presets
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
mod oidc;
|
||||||
mod saml;
|
mod saml;
|
||||||
|
|
||||||
|
pub use oidc::AuthentikOidc;
|
||||||
pub use saml::AuthentikSaml;
|
pub use saml::AuthentikSaml;
|
||||||
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
|
@ -19,6 +21,9 @@ use std::net::{IpAddr, Ipv4Addr};
|
||||||
const AUTHENTIK_IMAGE: &str = "ghcr.io/goauthentik/server:2026.8.0";
|
const AUTHENTIK_IMAGE: &str = "ghcr.io/goauthentik/server:2026.8.0";
|
||||||
const POSTGRES_IMAGE: &str = "docker.io/library/postgres:16-alpine";
|
const POSTGRES_IMAGE: &str = "docker.io/library/postgres:16-alpine";
|
||||||
|
|
||||||
|
pub(super) const SIGNING_CERT: &str = include_str!("../../../certificates/authentik/public.crt");
|
||||||
|
pub(super) const SIGNING_KEY: &str = include_str!("../../../certificates/authentik/private.key");
|
||||||
|
|
||||||
pub(super) const AUTHENTIK_PORT: u16 = 9000;
|
pub(super) const AUTHENTIK_PORT: u16 = 9000;
|
||||||
|
|
||||||
const AUTHENTIK_TOKEN: &str = "haze";
|
const AUTHENTIK_TOKEN: &str = "haze";
|
||||||
|
|
@ -90,6 +95,9 @@ async fn spawn_authentik(
|
||||||
let name = container_name(cloud_id, role);
|
let name = container_name(cloud_id, role);
|
||||||
|
|
||||||
let haze_directory = config.work_dir.join(cloud_id).join("authentik");
|
let haze_directory = config.work_dir.join(cloud_id).join("authentik");
|
||||||
|
write_file(&haze_directory, "public.crt", SIGNING_CERT)?;
|
||||||
|
write_file(&haze_directory, "private.key", SIGNING_KEY)?;
|
||||||
|
|
||||||
let blueprints_directory = haze_directory.join("blueprints");
|
let blueprints_directory = haze_directory.join("blueprints");
|
||||||
write_file(&blueprints_directory, "nextcloud.yaml", BLUEPRINT)?;
|
write_file(&blueprints_directory, "nextcloud.yaml", BLUEPRINT)?;
|
||||||
|
|
||||||
|
|
@ -236,6 +244,31 @@ impl ServiceTrait for Authentik {
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn post_setup(
|
||||||
|
&self,
|
||||||
|
docker: &Docker,
|
||||||
|
cloud_id: &str,
|
||||||
|
config: &HazeConfig,
|
||||||
|
) -> Result<Vec<Vec<String>>> {
|
||||||
|
let authentik_container = container_name(cloud_id, "server");
|
||||||
|
|
||||||
|
let authentik_url = config.proxy.addr_with_port(
|
||||||
|
&authentik_container,
|
||||||
|
container_ip(docker, &authentik_container, None).await?,
|
||||||
|
AUTHENTIK_PORT,
|
||||||
|
);
|
||||||
|
let nextcloud_url = config.proxy.addr(
|
||||||
|
cloud_id,
|
||||||
|
container_ip(docker, cloud_id, Some("haze")).await?,
|
||||||
|
);
|
||||||
|
|
||||||
|
let haze_directory = config.work_dir.join(cloud_id).join("authentik");
|
||||||
|
write_file(&haze_directory, "authentik-url", &authentik_url)?;
|
||||||
|
write_file(&haze_directory, "nextcloud-url", &nextcloud_url)?;
|
||||||
|
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
|
||||||
fn container_name(&self, cloud_id: &str) -> Option<String> {
|
fn container_name(&self, cloud_id: &str) -> Option<String> {
|
||||||
Some(container_name(cloud_id, "server"))
|
Some(container_name(cloud_id, "server"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
86
src/service/authentik/oidc.rs
Normal file
86
src/service/authentik/oidc.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
use super::{AUTHENTIK_PORT, container_ip, container_name, write_file};
|
||||||
|
use crate::Result;
|
||||||
|
use crate::cloud::CloudOptions;
|
||||||
|
use crate::config::HazeConfig;
|
||||||
|
use crate::service::{ServiceTrait, split_cmnd};
|
||||||
|
use bollard::Docker;
|
||||||
|
|
||||||
|
const BLUEPRINT: &str = include_str!("../../../blueprints/authentik-oidc.yaml");
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub struct AuthentikOidc;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl ServiceTrait for AuthentikOidc {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"oidc"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn spawn(
|
||||||
|
&self,
|
||||||
|
_docker: &Docker,
|
||||||
|
cloud_id: &str,
|
||||||
|
_network: &str,
|
||||||
|
config: &HazeConfig,
|
||||||
|
_options: &CloudOptions,
|
||||||
|
) -> Result<Vec<String>> {
|
||||||
|
let blueprints_directory = config
|
||||||
|
.work_dir
|
||||||
|
.join(cloud_id)
|
||||||
|
.join("authentik")
|
||||||
|
.join("blueprints");
|
||||||
|
write_file(&blueprints_directory, "oidc.yaml", BLUEPRINT)?;
|
||||||
|
|
||||||
|
Ok(Vec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apps(&self) -> &'static [&'static str] {
|
||||||
|
&["user_oidc"]
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn post_setup(
|
||||||
|
&self,
|
||||||
|
docker: &Docker,
|
||||||
|
cloud_id: &str,
|
||||||
|
config: &HazeConfig,
|
||||||
|
) -> Result<Vec<Vec<String>>> {
|
||||||
|
let authentik_container = container_name(cloud_id, "server");
|
||||||
|
let authentik_url = config.proxy.addr_with_port(
|
||||||
|
&authentik_container,
|
||||||
|
container_ip(docker, &authentik_container, None).await?,
|
||||||
|
AUTHENTIK_PORT,
|
||||||
|
);
|
||||||
|
|
||||||
|
let allow_insecure = if config.proxy.https { "0" } else { "1" };
|
||||||
|
|
||||||
|
Ok(vec![
|
||||||
|
split_cmnd(&format!(
|
||||||
|
"occ config:app:set --silent user_oidc allow_insecure_http --value {allow_insecure}"
|
||||||
|
)),
|
||||||
|
split_cmnd(
|
||||||
|
"occ config:app:set --silent user_oidc allow_multiple_user_backends --value 1",
|
||||||
|
),
|
||||||
|
vec![
|
||||||
|
"occ".into(),
|
||||||
|
"user_oidc:provider".into(),
|
||||||
|
"Authentik OIDC".into(),
|
||||||
|
"--clientid=nextcloud".into(),
|
||||||
|
"--clientsecret=haze-oidc-secret".into(),
|
||||||
|
format!(
|
||||||
|
"--discoveryuri={authentik_url}/application/o/nextcloud-oidc/.well-known/openid-configuration"
|
||||||
|
),
|
||||||
|
format!(
|
||||||
|
"--endsessionendpointuri={authentik_url}/application/o/nextcloud-oidc/end-session/"
|
||||||
|
),
|
||||||
|
"--scope".into(),
|
||||||
|
"openid email profile nextcloud".into(),
|
||||||
|
"--mapping-uid=nextcloud_uid".into(),
|
||||||
|
"--mapping-display-name=name".into(),
|
||||||
|
"--mapping-email=email".into(),
|
||||||
|
"--mapping-groups=groups".into(),
|
||||||
|
"--group-provisioning=1".into(),
|
||||||
|
"--unique-uid=0".into(),
|
||||||
|
],
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,11 +2,10 @@ use super::{AUTHENTIK_PORT, container_ip, container_name, write_file};
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use crate::cloud::CloudOptions;
|
use crate::cloud::CloudOptions;
|
||||||
use crate::config::{HazeConfig, ProxyConfig};
|
use crate::config::{HazeConfig, ProxyConfig};
|
||||||
|
use crate::service::authentik::SIGNING_CERT;
|
||||||
use crate::service::{ServiceTrait, split_cmnd};
|
use crate::service::{ServiceTrait, split_cmnd};
|
||||||
use bollard::Docker;
|
use bollard::Docker;
|
||||||
|
|
||||||
const SIGNING_CERT: &str = include_str!("../../../certificates/authentik/public.crt");
|
|
||||||
const SIGNING_KEY: &str = include_str!("../../../certificates/authentik/private.key");
|
|
||||||
const BLUEPRINT: &str = include_str!("../../../blueprints/authentik-saml.yaml");
|
const BLUEPRINT: &str = include_str!("../../../blueprints/authentik-saml.yaml");
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
|
@ -26,10 +25,6 @@ impl ServiceTrait for AuthentikSaml {
|
||||||
config: &HazeConfig,
|
config: &HazeConfig,
|
||||||
_options: &CloudOptions,
|
_options: &CloudOptions,
|
||||||
) -> Result<Vec<String>> {
|
) -> Result<Vec<String>> {
|
||||||
let haze_directory = config.work_dir.join(cloud_id).join("authentik");
|
|
||||||
write_file(&haze_directory, "public.crt", SIGNING_CERT)?;
|
|
||||||
write_file(&haze_directory, "private.key", SIGNING_KEY)?;
|
|
||||||
|
|
||||||
let blueprints_directory = config
|
let blueprints_directory = config
|
||||||
.work_dir
|
.work_dir
|
||||||
.join(cloud_id)
|
.join(cloud_id)
|
||||||
|
|
@ -51,20 +46,11 @@ impl ServiceTrait for AuthentikSaml {
|
||||||
config: &HazeConfig,
|
config: &HazeConfig,
|
||||||
) -> Result<Vec<Vec<String>>> {
|
) -> Result<Vec<Vec<String>>> {
|
||||||
let authentik_container = container_name(cloud_id, "server");
|
let authentik_container = container_name(cloud_id, "server");
|
||||||
|
|
||||||
let authentik_url = config.proxy.addr_with_port(
|
let authentik_url = config.proxy.addr_with_port(
|
||||||
&authentik_container,
|
&authentik_container,
|
||||||
container_ip(docker, &authentik_container, None).await?,
|
container_ip(docker, &authentik_container, None).await?,
|
||||||
AUTHENTIK_PORT,
|
AUTHENTIK_PORT,
|
||||||
);
|
);
|
||||||
let nextcloud_url = config.proxy.addr(
|
|
||||||
cloud_id,
|
|
||||||
container_ip(docker, cloud_id, Some("haze")).await?,
|
|
||||||
);
|
|
||||||
|
|
||||||
let haze_directory = config.work_dir.join(cloud_id).join("authentik");
|
|
||||||
write_file(&haze_directory, "authentik-url", &authentik_url)?;
|
|
||||||
write_file(&haze_directory, "nextcloud-url", &nextcloud_url)?;
|
|
||||||
|
|
||||||
Ok(vec![
|
Ok(vec![
|
||||||
split_cmnd("occ config:app:set --silent user_saml type --value saml"),
|
split_cmnd("occ config:app:set --silent user_saml type --value saml"),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue