Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rebase logout path (#96) for multiple open id providers (#99) #100

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ The plugin is configured via the `envoy.yaml`-file. The following configuration
| `id_token_header_name` | `string` | If set, this name will be used to forward the id token to the backend. | `X-Id-Token` | ❌ |
| `id_token_header_prefix` | `string` | The prefix of the header, that is used to forward the id token, if empty "" is used. | `Bearer ` | ❌ |
| `cookie_name` | `string` | The name of the cookie, that is used to store the session. | `oidcSession` | ✅ |
| `logout_path` | `string` | The path, that is used to logout the user. The user will be redirected to `end_session_endpoint` of the OIDC provider, if the server supports this; alternatively the user is sent to "/" | `/logout` | ✅ |
| `filter_plugin_cookies` | `bool` | Whether to filter the cookies that are managed and controlled by the plugin (namely cookie_name and `nonce`). | `true` | ✅ |
| `cookie_duration` | `u64` | The duration in seconds, after which the session cookie expires. | `86400` | ✅ |
| `token_validation` | bool | Whether to validate the token or not. | `true` | ✅ |
Expand Down
1 change: 1 addition & 0 deletions demo/configmap.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ data:
id_token_header_prefix: "Bearer "

cookie_name: "oidcSession"
logout_path: "/logout"
filter_plugin_cookies: true # or false
cookie_duration: 8640000 # in seconds
token_validation: true # or false
Expand Down
1 change: 1 addition & 0 deletions envoy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ static_resources:
id_token_header_prefix: "Bearer "

cookie_name: "oidcSession" # max. 32 characters
logout_path: "/logout"
filter_plugin_cookies: true # or false
cookie_duration: 8640000 # in seconds
token_validation: true # or false
Expand Down
4 changes: 2 additions & 2 deletions k8s/configmap.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ data:
id_token_header_prefix: "Bearer "

cookie_name: "oidcSession" # max. 32 characters
filter_plugin_cookies: true # or false
cookie_duration: 86400 # in seconds
logout_path: "/logout"
cookie_duration: 8640000 # in seconds
token_validation: true # or false
aes_key: "i-am-a-forty-four-characters-long-string-key" # generate with `openssl rand -base64 32`

Expand Down
55 changes: 55 additions & 0 deletions src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,17 @@ impl HttpContext for ConfiguredOidc {
return Action::Continue;
}

// If Path is logout route, clear cookies and redirect to base path
if path == self.plugin_config.logout_path {
match self.logout() {
Ok(action) => return action,
Err(e) => {
warn!("logout failed: {}", e);
self.show_error_page(503, "Logout failed", "Please try again, delete your cookies or contact your system administrator.");
}
}
}

// If the path matches the provider selection endpoint, redirect to the authorization endpoint
// with the selected provider.
if path.contains("/_wasm-oidc-plugin/provider-selection") {
Expand Down Expand Up @@ -553,6 +564,50 @@ impl ConfiguredOidc {
}
}

/// Clear the cookies and redirect to the base path or `end_session_endpoint`.
fn logout(&self) -> Result<Action, PluginError> {
let cookie_values = Session::make_cookie_values(
"",
"",
&self.plugin_config.cookie_name,
0,
self.get_number_of_cookies() as u64,
);

let mut headers = Session::make_set_cookie_headers(&cookie_values);

// Get session from cookie
let cookie = self.get_session_cookie_as_string()?;
let nonce = self.get_nonce()?;
let session = Session::decode_and_decrypt(
cookie,
self.plugin_config.aes_key.reveal().clone(),
nonce,
)?;

// Get provider to use based on issuer because the end session endpoint is provider-specific
let provider = self
.open_id_providers
.iter()
.find(|provider| provider.issuer == session.issuer.clone().unwrap())
.unwrap();
// TODO: Error handling

// Redirect to end session endpoint, if available (not all OIDC providers support this)
let location = match &provider.end_session_endpoint {
// if the end session endpoint is available, redirect to it
Some(url) => url.as_str(),
// else, redirect to the base path
None => "/",
};
headers.push(("Location", location));
headers.push(("Cache-Control", "no-cache"));

self.send_http_response(307, headers, Some(b"Logging out..."));

Ok(Action::Pause)
}

/// Show the auth page or redirect to the authorization endpoint.
fn generate_auth_page(&self) {
// If there is more than one provider, show an auth page where the user selects the provider
Expand Down
5 changes: 3 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ use regex::Regex;
// url
use url::Url;

/// Struct that holds the configuration for the plugin. It is loaded from the config file
/// `envoy.yaml`
/// Struct that holds the configuration for the plugin. It is loaded from the config file `envoy.yaml`
#[derive(Clone, Debug, Deserialize)]
pub struct PluginConfiguration {
// OpenID Connect Configuration
Expand Down Expand Up @@ -59,6 +58,8 @@ pub struct PluginConfiguration {
// Cookie settings
/// The cookie name that will be used for the session cookie
pub cookie_name: String,
/// The URL to logout the user
pub logout_path: String,
/// Filter out the cookies created and controlled by the plugin
/// If the value is true, the cookies will be filtered out
pub filter_plugin_cookies: bool,
Expand Down
16 changes: 16 additions & 0 deletions src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ pub struct OpenIdProvider {
pub auth_endpoint: Url,
/// The URL of the token endpoint
pub token_endpoint: Url,
/// The URL of the end session endpoint
pub end_session_endpoint: Option<Url>,
/// The issuer that will be used for the token request
pub issuer: String,
/// The public keys that will be used for the validation of the ID Token
Expand Down Expand Up @@ -459,6 +461,7 @@ impl Context for Root {
open_id_config: resolver_to_update.open_id_config.clone(),
auth_endpoint: open_id_response.authorization_endpoint.clone(),
token_endpoint: open_id_response.token_endpoint.clone(),
end_session_endpoint: open_id_response.end_session_endpoint.clone(),
issuer: open_id_response.issuer.clone(),
public_keys: keys,
});
Expand Down Expand Up @@ -512,6 +515,19 @@ impl Root {
return Err(PluginError::ConfigError("`cookie_name` is empty or not valid meaning that it contains invalid characters like ;, =, :, /, space".to_string()));
}

// Logout Path
if plugin_config.logout_path.is_empty() {
return Err(PluginError::ConfigError(
"`logout_path` is empty".to_string(),
));
}

if !plugin_config.logout_path.starts_with('/') {
return Err(PluginError::ConfigError(
"`logout_path` does not start with a `/`".to_string(),
));
}

// Cookie Duration
if plugin_config.cookie_duration == 0 {
return Err(PluginError::ConfigError(
Expand Down
2 changes: 2 additions & 0 deletions src/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ pub struct OpenIdDiscoveryResponse {
pub authorization_endpoint: Url,
/// The token endpoint to exchange the code for a token
pub token_endpoint: Url,
/// The URL to logout the user
pub end_session_endpoint: Option<Url>,
/// The jwks uri to load the jwks response from
pub jwks_uri: Url,
}
Expand Down
Loading