Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e029e94f0d | ||
|
|
3be2b5163b | ||
|
|
6a145f58ff | ||
|
|
f1cede2ebd | ||
|
|
a5cfa1d412 | ||
|
|
a0674654c1 | ||
|
|
3faa1c58c1 | ||
|
|
7e296f34af | ||
|
|
9f8ced190c | ||
|
|
c194bb16d8 | ||
|
|
39fec9b55e | ||
|
|
e97ed9888d | ||
|
|
559102ffe3 | ||
|
|
6bf80ddcc7 | ||
|
|
89dbe1b4d9 | ||
|
|
334e16d646 | ||
|
|
a7bbe519f4 | ||
|
|
5827486c5a | ||
|
|
8ca8f7eddd | ||
|
|
0600276b43 | ||
|
|
a77a1495c7 |
26
Cargo.lock
generated
@@ -41,7 +41,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alerter"
|
||||
version = "1.15.0"
|
||||
version = "1.15.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -943,7 +943,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "command"
|
||||
version = "1.15.0"
|
||||
version = "1.15.2"
|
||||
dependencies = [
|
||||
"komodo_client",
|
||||
"run_command",
|
||||
@@ -1355,7 +1355,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "environment_file"
|
||||
version = "1.15.0"
|
||||
version = "1.15.2"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
@@ -1439,7 +1439,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "formatting"
|
||||
version = "1.15.0"
|
||||
version = "1.15.2"
|
||||
dependencies = [
|
||||
"serror",
|
||||
]
|
||||
@@ -1571,7 +1571,7 @@ checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
|
||||
|
||||
[[package]]
|
||||
name = "git"
|
||||
version = "1.15.0"
|
||||
version = "1.15.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"command",
|
||||
@@ -2192,7 +2192,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_cli"
|
||||
version = "1.15.0"
|
||||
version = "1.15.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@@ -2208,7 +2208,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_client"
|
||||
version = "1.15.0"
|
||||
version = "1.15.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2239,7 +2239,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_core"
|
||||
version = "1.15.0"
|
||||
version = "1.15.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2296,7 +2296,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "komodo_periphery"
|
||||
version = "1.15.0"
|
||||
version = "1.15.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async_timing_util",
|
||||
@@ -2382,7 +2382,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "logger"
|
||||
version = "1.15.0"
|
||||
version = "1.15.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"komodo_client",
|
||||
@@ -2446,7 +2446,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "migrator"
|
||||
version = "1.15.0"
|
||||
version = "1.15.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"dotenvy",
|
||||
@@ -3101,7 +3101,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "periphery_client"
|
||||
version = "1.15.0"
|
||||
version = "1.15.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"komodo_client",
|
||||
@@ -4879,7 +4879,7 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "update_logger"
|
||||
version = "1.15.0"
|
||||
version = "1.15.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"komodo_client",
|
||||
|
||||
@@ -3,7 +3,7 @@ resolver = "2"
|
||||
members = ["bin/*", "lib/*", "client/core/rs", "client/periphery/rs"]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.15.0"
|
||||
version = "1.15.2"
|
||||
edition = "2021"
|
||||
authors = ["mbecker20 <becker.maxh@gmail.com>"]
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
@@ -32,17 +32,15 @@ pub async fn init_default_oidc_client() {
|
||||
return;
|
||||
}
|
||||
async {
|
||||
let provider = config.oidc_provider.to_string();
|
||||
// Use OpenID Connect Discovery to fetch the provider metadata.
|
||||
let provider_metadata = CoreProviderMetadata::discover_async(
|
||||
IssuerUrl::new(if provider.ends_with('/') {
|
||||
provider
|
||||
} else {
|
||||
provider + "/"
|
||||
})?,
|
||||
IssuerUrl::new(config.oidc_provider.clone())?,
|
||||
async_http_client,
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
.context(
|
||||
"Failed to get OIDC /.well-known/openid-configuration",
|
||||
)?;
|
||||
|
||||
// Create an OpenID Connect client by specifying the client ID, client secret, authorization URL
|
||||
// and token URL.
|
||||
|
||||
@@ -135,7 +135,9 @@ async fn callback(
|
||||
.context("CSRF Token invalid")?;
|
||||
|
||||
if komodo_timestamp() > valid_until {
|
||||
return Err(anyhow!("CSRF token invalid (Timed out). The token must be "));
|
||||
return Err(anyhow!(
|
||||
"CSRF token invalid (Timed out). The token must be "
|
||||
));
|
||||
}
|
||||
|
||||
let token_response = client
|
||||
@@ -150,8 +152,21 @@ async fn callback(
|
||||
let id_token = token_response
|
||||
.id_token()
|
||||
.context("OIDC Server did not return an ID token")?;
|
||||
|
||||
// Some providers attach additional audiences, they must be added here
|
||||
// so token verification succeeds.
|
||||
let verifier = client.id_token_verifier();
|
||||
let additional_audiences = &core_config().oidc_additional_audiences;
|
||||
let verifier = if additional_audiences.is_empty() {
|
||||
verifier
|
||||
} else {
|
||||
verifier.set_other_audience_verifier_fn(|aud| {
|
||||
additional_audiences.contains(aud)
|
||||
})
|
||||
};
|
||||
|
||||
let claims = id_token
|
||||
.claims(&client.id_token_verifier(), &nonce)
|
||||
.claims(&verifier, &nonce)
|
||||
.context("Failed to verify token claims")?;
|
||||
|
||||
// Verify the access token hash to ensure that the access token hasn't been substituted for
|
||||
@@ -191,20 +206,25 @@ async fn callback(
|
||||
if !no_users_exist && core_config.disable_user_registration {
|
||||
return Err(anyhow!("User registration is disabled"));
|
||||
}
|
||||
// Email will use user_id if it isn't available.
|
||||
let email = claims
|
||||
.email()
|
||||
.map(|email| email.as_str())
|
||||
.unwrap_or(user_id);
|
||||
let username = if core_config.oidc_use_full_email {
|
||||
email
|
||||
} else {
|
||||
email
|
||||
.split_once('@')
|
||||
.map(|(username, _)| username)
|
||||
.unwrap_or(email)
|
||||
}
|
||||
.to_string();
|
||||
// Will use preferred_username, then email, then user_id if it isn't available.
|
||||
let username = claims
|
||||
.preferred_username()
|
||||
.map(|username| username.to_string())
|
||||
.unwrap_or_else(|| {
|
||||
let email = claims
|
||||
.email()
|
||||
.map(|email| email.as_str())
|
||||
.unwrap_or(user_id);
|
||||
if core_config.oidc_use_full_email {
|
||||
email
|
||||
} else {
|
||||
email
|
||||
.split_once('@')
|
||||
.map(|(username, _)| username)
|
||||
.unwrap_or(email)
|
||||
}
|
||||
.to_string()
|
||||
});
|
||||
let user = User {
|
||||
id: Default::default(),
|
||||
username,
|
||||
|
||||
@@ -87,6 +87,9 @@ pub fn core_config() -> &'static CoreConfig {
|
||||
.unwrap_or(config.oidc_client_secret),
|
||||
oidc_use_full_email: env.komodo_oidc_use_full_email
|
||||
.unwrap_or(config.oidc_use_full_email),
|
||||
oidc_additional_audiences: maybe_read_list_from_file(env.komodo_oidc_additional_audiences_file,env
|
||||
.komodo_oidc_additional_audiences)
|
||||
.unwrap_or(config.oidc_additional_audiences),
|
||||
google_oauth: OauthCredentials {
|
||||
enabled: env
|
||||
.komodo_google_oauth_enabled
|
||||
|
||||
@@ -116,6 +116,10 @@ pub struct Env {
|
||||
pub komodo_oidc_client_secret_file: Option<PathBuf>,
|
||||
/// Override `oidc_use_full_email`
|
||||
pub komodo_oidc_use_full_email: Option<bool>,
|
||||
/// Override `oidc_additional_audiences`
|
||||
pub komodo_oidc_additional_audiences: Option<Vec<String>>,
|
||||
/// Override `oidc_additional_audiences` from file
|
||||
pub komodo_oidc_additional_audiences_file: Option<PathBuf>,
|
||||
|
||||
/// Override `google_oauth.enabled`
|
||||
pub komodo_google_oauth_enabled: Option<bool>,
|
||||
@@ -159,31 +163,31 @@ pub struct Env {
|
||||
pub komodo_github_webhook_app_pk_path: Option<String>,
|
||||
|
||||
/// Override `database.uri`
|
||||
#[serde(alias = "KOMODO_MONGO_URI")]
|
||||
#[serde(alias = "komodo_mongo_uri")]
|
||||
pub komodo_database_uri: Option<String>,
|
||||
/// Override `database.uri` from file
|
||||
#[serde(alias = "KOMODO_MONGO_URI_FILE")]
|
||||
#[serde(alias = "komodo_mongo_uri_file")]
|
||||
pub komodo_database_uri_file: Option<PathBuf>,
|
||||
/// Override `database.address`
|
||||
#[serde(alias = "KOMODO_MONGO_ADDRESS")]
|
||||
#[serde(alias = "komodo_mongo_address")]
|
||||
pub komodo_database_address: Option<String>,
|
||||
/// Override `database.username`
|
||||
#[serde(alias = "KOMODO_MONGO_USERNAME")]
|
||||
#[serde(alias = "komodo_mongo_username")]
|
||||
pub komodo_database_username: Option<String>,
|
||||
/// Override `database.username` with file
|
||||
#[serde(alias = "KOMODO_MONGO_USERNAME_FILE")]
|
||||
#[serde(alias = "komodo_mongo_username_file")]
|
||||
pub komodo_database_username_file: Option<PathBuf>,
|
||||
/// Override `database.password`
|
||||
#[serde(alias = "KOMODO_MONGO_PASSWORD")]
|
||||
#[serde(alias = "komodo_mongo_password")]
|
||||
pub komodo_database_password: Option<String>,
|
||||
/// Override `database.password` with file
|
||||
#[serde(alias = "KOMODO_MONGO_PASSWORD_FILE")]
|
||||
#[serde(alias = "komodo_mongo_password_file")]
|
||||
pub komodo_database_password_file: Option<PathBuf>,
|
||||
/// Override `database.app_name`
|
||||
#[serde(alias = "KOMODO_MONGO_APP_NAME")]
|
||||
#[serde(alias = "komodo_mongo_app_name")]
|
||||
pub komodo_database_app_name: Option<String>,
|
||||
/// Override `database.db_name`
|
||||
#[serde(alias = "KOMODO_MONGO_DB_NAME")]
|
||||
#[serde(alias = "komodo_mongo_db_name")]
|
||||
pub komodo_database_db_name: Option<String>,
|
||||
|
||||
/// Override `aws.access_key_id`
|
||||
@@ -344,6 +348,11 @@ pub struct CoreConfig {
|
||||
#[serde(default)]
|
||||
pub oidc_use_full_email: bool,
|
||||
|
||||
/// Your OIDC provider may set additional audiences other than `client_id`,
|
||||
/// they must be added here to make claims verification work.
|
||||
#[serde(default)]
|
||||
pub oidc_additional_audiences: Vec<String>,
|
||||
|
||||
// =========
|
||||
// = Oauth =
|
||||
// =========
|
||||
@@ -548,6 +557,11 @@ impl CoreConfig {
|
||||
&config.oidc_client_secret,
|
||||
),
|
||||
oidc_use_full_email: config.oidc_use_full_email,
|
||||
oidc_additional_audiences: config
|
||||
.oidc_additional_audiences
|
||||
.iter()
|
||||
.map(|aud| empty_or_redacted(aud))
|
||||
.collect(),
|
||||
google_oauth: OauthCredentials {
|
||||
enabled: config.google_oauth.enabled,
|
||||
id: empty_or_redacted(&config.google_oauth.id),
|
||||
|
||||
@@ -82,6 +82,9 @@ KOMODO_OIDC_ENABLED=false
|
||||
# KOMODO_OIDC_CLIENT_SECRET= # Alt: KOMODO_OIDC_CLIENT_SECRET_FILE
|
||||
## Make usernames the full email.
|
||||
# KOMODO_OIDC_USE_FULL_EMAIL=true
|
||||
## Add additional trusted audiences for token claims verification.
|
||||
## Supports comma separated list, and passing with _FILE (for compose secrets).
|
||||
# KOMODO_OIDC_ADDITIONAL_AUDIENCES=abc,123 # Alt: KOMODO_OIDC_ADDITIONAL_AUDIENCES_FILE
|
||||
|
||||
## Github Oauth
|
||||
KOMODO_GITHUB_OAUTH_ENABLED=false
|
||||
|
||||
@@ -161,10 +161,17 @@ oidc_client_secret = ""
|
||||
## If true, use the full email for usernames.
|
||||
## Otherwise, the @address will be stripped,
|
||||
## making usernames more concise.
|
||||
## Default: false.
|
||||
## Env: KOMODO_OIDC_USE_FULL_EMAIL
|
||||
## Default: false.
|
||||
oidc_use_full_email = false
|
||||
|
||||
## Some providers attach other audiences in addition to the client_id.
|
||||
## If you have this issue, `Invalid audiences: `...` is not a trusted audience"`,
|
||||
## you can add the audience `...` to the list here (assuming it should be trusted).
|
||||
## Env: KOMODO_OIDC_ADDITIONAL_AUDIENCES or KOMODO_OIDC_ADDITIONAL_AUDIENCES_FILE
|
||||
## Default: empty
|
||||
oidc_additional_audiences = []
|
||||
|
||||
#########
|
||||
# OAUTH #
|
||||
#########
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
# Advanced Configuration
|
||||
|
||||
### Oauth2
|
||||
### OIDC / Oauth2
|
||||
|
||||
To enable OAuth2 login, you must create a client on the respective OAuth provider,
|
||||
for example [Github](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app)
|
||||
or [Google](https://developers.google.com/identity/protocols/oauth2).
|
||||
|
||||
Komodo also supports self hosted Oauth2 providers like [Authentik](https://docs.goauthentik.io/docs/providers/oauth2/) or [Gitea](https://docs.gitea.com/development/oauth2-provider).
|
||||
|
||||
- Komodo uses the `web application` login flow.
|
||||
- The redirect uri is:
|
||||
- `<KOMODO_HOST>/auth/github/callback` for Github.
|
||||
- `<KOMODO_HOST>/auth/google/callback` for Google.
|
||||
- `<KOMODO_HOST>/auth/oidc/callback` for OIDC.
|
||||
|
||||
### Mount a config file
|
||||
|
||||
|
||||
@@ -693,6 +693,7 @@ export const StackConfig = ({
|
||||
}
|
||||
onValueChange={(file_contents) => set({ file_contents })}
|
||||
language="yaml"
|
||||
readOnly={disabled}
|
||||
/>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -27,9 +27,7 @@ export const Dashboard = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-12">
|
||||
<ActiveResources />
|
||||
</div>
|
||||
<ActiveResources />
|
||||
<Page
|
||||
title="Dashboard"
|
||||
icon={<Box className="w-8 h-8" />}
|
||||
@@ -253,37 +251,39 @@ const ActiveResources = () => {
|
||||
if (resources.length === 0) return null;
|
||||
|
||||
return (
|
||||
<Section
|
||||
title="Active"
|
||||
icon={
|
||||
<Circle className="w-4 h-4 stroke-none transition-colors fill-green-500" />
|
||||
}
|
||||
>
|
||||
<DataTable
|
||||
tableKey="active-resources"
|
||||
data={resources}
|
||||
columns={[
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Name" />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<ResourceLink type={row.original.type} id={row.original.id} />
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "type",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Resource" />
|
||||
),
|
||||
},
|
||||
{
|
||||
header: "State",
|
||||
cell: ({ row }) => row.original.state,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Section>
|
||||
<div className="mb-12">
|
||||
<Section
|
||||
title="Active"
|
||||
icon={
|
||||
<Circle className="w-4 h-4 stroke-none transition-colors fill-green-500" />
|
||||
}
|
||||
>
|
||||
<DataTable
|
||||
tableKey="active-resources"
|
||||
data={resources}
|
||||
columns={[
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Name" />
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<ResourceLink type={row.original.type} id={row.original.id} />
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "type",
|
||||
header: ({ column }) => (
|
||||
<SortableHeader column={column} title="Resource" />
|
||||
),
|
||||
},
|
||||
{
|
||||
header: "State",
|
||||
cell: ({ row }) => row.original.state,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
22
readme.md
@@ -6,6 +6,8 @@ A tool to build and deploy software across many servers.
|
||||
|
||||
🦎 [Try the Demo](https://demo.komo.do)
|
||||
|
||||
🦎 [See the Build Server](https://build.komo.do)
|
||||
|
||||
🦎 [Join the Discord](https://discord.gg/DRqE8Fvg5c)
|
||||
|
||||
## About
|
||||
@@ -31,25 +33,21 @@ there are no warranties. Use at your own risk.
|
||||
### Light Theme
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
### Dark Theme
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
BIN
screenshots/Dark-Compose.png
Normal file
|
After Width: | Height: | Size: 572 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 368 KiB |
|
Before Width: | Height: | Size: 213 KiB |
|
Before Width: | Height: | Size: 197 KiB |
BIN
screenshots/Dark-Env.png
Normal file
|
After Width: | Height: | Size: 434 KiB |
|
Before Width: | Height: | Size: 223 KiB After Width: | Height: | Size: 487 KiB |
|
Before Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 140 KiB |
BIN
screenshots/Dark-Stack.png
Normal file
|
After Width: | Height: | Size: 405 KiB |
BIN
screenshots/Dark-Stats.png
Normal file
|
After Width: | Height: | Size: 405 KiB |
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 476 KiB |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 451 KiB |
|
Before Width: | Height: | Size: 133 KiB |
BIN
screenshots/Light-Compose.png
Normal file
|
After Width: | Height: | Size: 580 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 375 KiB |
|
Before Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 182 KiB |
BIN
screenshots/Light-Env.png
Normal file
|
After Width: | Height: | Size: 434 KiB |
|
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 495 KiB |
|
Before Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 146 KiB |
BIN
screenshots/Light-Stack.png
Normal file
|
After Width: | Height: | Size: 416 KiB |
BIN
screenshots/Light-Stats.png
Normal file
|
After Width: | Height: | Size: 402 KiB |
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 409 KiB |
|
Before Width: | Height: | Size: 186 KiB After Width: | Height: | Size: 461 KiB |
|
Before Width: | Height: | Size: 140 KiB |