diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 2542b899f624..307ab0bf68ea 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -314,7 +314,7 @@ jobs: name: Release ${{ needs.check-inputs.outputs.version }} draft: true prerelease: ${{ inputs.release_reason == 'prerelease' }} - generate_release_notes: true + generate_release_notes: false body: | See ${{ needs.check-inputs.outputs.changelog-url }} files: | diff --git a/SECURITY.md b/SECURITY.md index 34f9b7d825f4..831f01a5dd88 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,4 +1,4 @@ -authentik takes security very seriously. We follow the rules of [responsible disclosure](https://en.wikipedia.org/wiki/Responsible_disclosure), and we urge our community to do so as well, instead of reporting vulnerabilities publicly. This allows us to patch the issue quickly, announce it's existence and release the fixed version. +authentik takes security very seriously. We follow the rules of [responsible disclosure](https://en.wikipedia.org/wiki/Responsible_disclosure), and we urge our community to do so as well, instead of reporting vulnerabilities publicly. This allows us to patch the issue quickly, announce its existence and release the fixed version. ## Independent audits and pentests @@ -6,7 +6,7 @@ We are committed to engaging in regular pentesting and security audits of authen ## What authentik classifies as a CVE -CVE (Common Vulnerability and Exposure) is a system designed to aggregate all vulnerabilities. As such, a CVE will be issued when there is a either vulnerability or exposure. Per NIST, A vulnerability is: +CVE (Common Vulnerability and Exposure) is a system designed to aggregate all vulnerabilities. As such, a CVE will be issued when there is either a vulnerability or exposure. Per NIST, A vulnerability is: “Weakness in an information system, system security procedures, internal controls, or implementation that could be exploited or triggered by a threat source.” @@ -25,10 +25,7 @@ Even if the issue is not a CVE, we still greatly appreciate your help in hardeni ## Reporting a Vulnerability -If you discover a potential vulnerability, please report it responsibly through one of the following channels: - -- **Email**: [security@goauthentik.io](mailto:security@goauthentik.io) -- **GitHub**: Submit a private security advisory via our [repository’s advisory portal](https://github.com/goauthentik/authentik/security/advisories/new) +If you discover a potential vulnerability, please report it responsibly by submitting a private security advisory via our [repository’s advisory portal](https://github.com/goauthentik/authentik/security/advisories/new). When submitting a report, please include as much detail as possible, such as: @@ -96,7 +93,7 @@ The destinations of outgoing network requests (HTTP, TCP, etc.) made by authenti ## Disclosure process -1. Report from Github or Issue is reported via Email as listed above. +1. Vulnerability is reported via a GitHub Security Advisory, as listed above. 2. The authentik Security team will try to reproduce the issue and ask for more information if required. 3. A severity level is assigned. 4. A fix is created, and if possible tested by the issue reporter. @@ -107,3 +104,9 @@ The destinations of outgoing network requests (HTTP, TCP, etc.) made by authenti ## Getting security notifications To get security notifications, subscribe to the mailing list [here](https://groups.google.com/g/authentik-security-announcements) or join the [discord](https://goauthentik.io/discord) server. + +## Contact + +For general inquiries, you can reach the authentik Security team at [security@goauthentik.io](mailto:security@goauthentik.io). + +_Please do not use email for vulnerability reports, instead use our [repository’s advisory portal](https://github.com/goauthentik/authentik/security/advisories/new)._ diff --git a/internal/web/proxy.go b/internal/web/proxy.go index 8092416c9eed..fb6add10e645 100644 --- a/internal/web/proxy.go +++ b/internal/web/proxy.go @@ -25,6 +25,25 @@ const ( maxBodyBytes = 32 * 1024 * 1024 ) +var djangoHTTPMethods = map[string]struct{}{ + http.MethodGet: {}, + http.MethodHead: {}, + http.MethodPost: {}, + http.MethodPut: {}, + http.MethodPatch: {}, + http.MethodDelete: {}, + http.MethodOptions: {}, + http.MethodTrace: {}, +} + +func handleUnsupportedHTTPMethod(rw http.ResponseWriter, r *http.Request) bool { + if _, ok := djangoHTTPMethods[r.Method]; ok { + return false + } + http.Error(rw, "Unsupported HTTP method.", http.StatusNotImplemented) + return true +} + func (ws *WebServer) configureProxy() { // Reverse proxy to the application server director := func(req *http.Request) { @@ -91,6 +110,10 @@ func (ws *WebServer) configureProxy() { return } + if handleUnsupportedHTTPMethod(rw, r) { + return + } + r.Body = http.MaxBytesReader(rw, r.Body, maxBodyBytes) rp.ServeHTTP(rw, r) diff --git a/src/server/core.rs b/src/server/core.rs index b634e46caedb..540a0de94fc4 100644 --- a/src/server/core.rs +++ b/src/server/core.rs @@ -12,10 +12,10 @@ use axum::{ body::Body, extract::{OriginalUri, Request, State}, http::{ - HeaderName, HeaderValue, StatusCode, Uri, + HeaderName, HeaderValue, Method, StatusCode, Uri, header::{ACCEPT, CONTENT_TYPE, HOST, LOCATION, RETRY_AFTER}, }, - response::Response, + response::{IntoResponse as _, Response}, routing::any, }; use http_body_util::BodyExt as _; @@ -61,6 +61,13 @@ const X_FORWARDED_CLIENT_CERT: HeaderName = HeaderName::from_static("x-forwarded const X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for"); const X_FORWARDED_PROTO: HeaderName = HeaderName::from_static("x-forwarded-proto"); +fn is_django_http_method(method: &Method) -> bool { + matches!( + method.as_str(), + "GET" | "HEAD" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "TRACE" + ) +} + const FORWARD_ALWAYS_REMOVED_HEADERS: [HeaderName; 7] = [ HeaderName::from_static("forwarded"), HeaderName::from_static("host"), @@ -112,6 +119,9 @@ async fn forward_request( if !GUNICORN_READY.load(Ordering::Relaxed) { return Ok(startup_response(&accept_header)); } + if !is_django_http_method(request.method()) { + return Ok((StatusCode::NOT_IMPLEMENTED, "Unsupported HTTP method.\n").into_response()); + } let uri = Uri::builder() .scheme("http") @@ -309,6 +319,37 @@ pub(super) fn build_router(server: &Arc) -> eyre::Result { Ok(router) } +#[cfg(test)] +mod tests { + use axum::http::Method; + + use super::is_django_http_method; + + #[test] + fn django_http_methods_are_allowed() { + for method in [ + Method::GET, + Method::HEAD, + Method::POST, + Method::PUT, + Method::PATCH, + Method::DELETE, + Method::OPTIONS, + Method::TRACE, + ] { + assert!(is_django_http_method(&method)); + } + } + + #[test] + fn unsupported_http_methods_are_rejected() { + assert!(!is_django_http_method(&Method::CONNECT)); + assert!(!is_django_http_method( + &Method::from_bytes(b"PROPFIND").expect("method") + )); + } +} + mod websockets { use std::sync::Arc; diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 3795bcc1f217..39c43caef6f4 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -462,55 +462,6 @@ importers: specifier: 'catalog:' version: 6.0.3 - packages/eslint-config: - dependencies: - eslint: - specifier: 'catalog:' - version: 9.39.5(jiti@2.7.0)(supports-color@10.2.2) - eslint-plugin-import: - specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2) - eslint-plugin-lit: - specifier: ^2.3.1 - version: 2.3.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2)) - eslint-plugin-react: - specifier: ^7.37.5 - version: 7.37.5(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2)) - eslint-plugin-react-hooks: - specifier: ^7.1.1 - version: 7.1.1(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2) - eslint-plugin-wc: - specifier: ^3.1.0 - version: 3.1.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2)) - react: - specifier: ^18.0.0 || ^19.0.0 - version: 19.2.8 - react-dom: - specifier: ^18.0.0 || ^19.0.0 - version: 19.2.8(react@19.2.8) - devDependencies: - '@goauthentik/prettier-config': - specifier: link:../prettier-config - version: link:../prettier-config - '@goauthentik/tsconfig': - specifier: link:../tsconfig - version: link:../tsconfig - '@types/eslint': - specifier: ^9.6.1 - version: 9.6.1 - '@types/node': - specifier: 'catalog:' - version: 26.1.2 - '@typescript-eslint/parser': - specifier: 'catalog:' - version: 8.65.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) - typescript: - specifier: ^6.0.3 - version: 6.0.3 - typescript-eslint: - specifier: 'catalog:' - version: 8.65.0(eslint@9.39.5(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) - packages/formdata-polyfill: {} packages/lex: {} @@ -2577,9 +2528,6 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - '@types/eslint@9.6.1': - resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} - '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} @@ -8656,11 +8604,6 @@ snapshots: '@types/deep-eql@4.0.2': {} - '@types/eslint@9.6.1': - dependencies: - '@types/estree': 1.0.9 - '@types/json-schema': 7.0.15 - '@types/estree-jsx@1.0.5': dependencies: '@types/estree': 1.0.9 diff --git a/web/src/user/ak-interface-user.ts b/web/src/user/ak-interface-user.ts index a49bdcce6cf1..2112b410fcc3 100644 --- a/web/src/user/ak-interface-user.ts +++ b/web/src/user/ak-interface-user.ts @@ -157,8 +157,9 @@ class UserInterface extends WithLicenseSummary( navItems.push({ label: msg("Discover"), link: "/requests" }); } if ( - this.can(CapabilitiesEnum.CanAgentSelfService) && - this.uiConfig.enabledFeatures.agents + this.licenseSummary?.status !== LicenseSummaryStatusEnum.Unlicensed && + this.uiConfig.enabledFeatures.agents && + this.can(CapabilitiesEnum.CanAgentSelfService) ) { navItems.push({ label: msg("Agents"), link: "/agents" }); }