Skip to content
Open
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
4 changes: 1 addition & 3 deletions .github/workflows/test-examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,7 @@ jobs:
"$MANAGEMENT_URL/v1/bootstrap" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"accept-terms-of-use": true
}' \
-d '{}' \
|| echo "Bootstrap may have already been completed"

- name: Dump docker logs on failure
Expand Down
15 changes: 1 addition & 14 deletions crates/lakekeeper/src/api/management/v1/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ pub enum AuthZBackend {
#[derive(Debug, Deserialize, utoipa::ToSchema, TypedBuilder)]
#[serde(rename_all = "kebab-case")]
pub struct BootstrapRequest {
/// Set to true if you accept LAKEKEEPER terms of use.
#[builder(setter(strip_bool))]
pub accept_terms_of_use: bool,
/// If set to true, the calling user is treated as an operator and obtain
/// a corresponding role. If not specified, the user is treated as a human.
#[serde(default)]
Expand Down Expand Up @@ -89,19 +86,9 @@ pub(crate) trait Service<C: Catalog, A: Authorizer, S: SecretStore> {
user_name,
user_email,
user_type,
accept_terms_of_use,
is_operator,
} = request;

if !accept_terms_of_use {
return Err(ErrorModel::builder()
.code(http::StatusCode::BAD_REQUEST.into())
.message("You must accept the terms of use to bootstrap the catalog.".to_string())
.r#type("TermsOfUseNotAccepted".to_string())
.build()
.into());
}

// ------------------- AUTHZ -------------------
// We check at two places if we can bootstrap: AuthZ and the catalog.
// AuthZ just checks if the request metadata could be added as the servers
Expand All @@ -111,7 +98,7 @@ pub(crate) trait Service<C: Catalog, A: Authorizer, S: SecretStore> {

// ------------------- Business Logic -------------------
let mut t = C::Transaction::begin_write(state.v1_state.catalog.clone()).await?;
let success = C::bootstrap(request.accept_terms_of_use, t.transaction()).await?;
let success = C::bootstrap(t.transaction()).await?;
if !success {
return Err(ErrorModel::bad_request(
"Catalog already bootstrapped",
Expand Down
12 changes: 4 additions & 8 deletions crates/lakekeeper/src/implementations/postgres/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,30 +37,27 @@
if let Some(server) = server {
Ok(ServerInfo::Bootstrapped {
server_id: server.server_id,
terms_accepted: server.terms_accepted,
})
} else {
Ok(ServerInfo::NotBootstrapped)
}
}

pub(super) async fn bootstrap<'e, 'c: 'e, E: sqlx::Executor<'c, Database = sqlx::Postgres>>(
terms_accepted: bool,
connection: E,
) -> Result<bool> {
let server_id = CONFIG.server_id;

let result = sqlx::query!(

Check failure on line 51 in crates/lakekeeper/src/implementations/postgres/bootstrap.rs

View workflow job for this annotation

GitHub Actions / check-generated-openapi-matches

set `DATABASE_URL` to use query macros online, or run `cargo sqlx prepare` to update the query cache

Check failure on line 51 in crates/lakekeeper/src/implementations/postgres/bootstrap.rs

View workflow job for this annotation

GitHub Actions / clippy

set `DATABASE_URL` to use query macros online, or run `cargo sqlx prepare` to update the query cache
r#"
INSERT INTO server (single_row, server_id, open_for_bootstrap, terms_accepted)
VALUES (true, $1, false, $2)
VALUES (true, $1, false, true)
ON CONFLICT (single_row)
DO UPDATE SET terms_accepted = $2, open_for_bootstrap = false
DO UPDATE SET open_for_bootstrap = false, terms_accepted = true
WHERE server.open_for_bootstrap = true
returning server_id
"#,
server_id,
terms_accepted,
)
.fetch_one(connection)
.await;
Expand Down Expand Up @@ -94,18 +91,17 @@
let data = get_validation_data(&state.read_pool()).await.unwrap();
assert_eq!(data, ServerInfo::NotBootstrapped);

let success = bootstrap(true, &state.read_write.write_pool).await.unwrap();
let success = bootstrap(&state.read_write.write_pool).await.unwrap();
assert!(success);
let data = get_validation_data(&state.read_pool()).await.unwrap();
assert_eq!(
data,
ServerInfo::Bootstrapped {
server_id: CONFIG.server_id,
terms_accepted: true,
}
);

let success = bootstrap(true, &state.read_write.write_pool).await.unwrap();
let success = bootstrap(&state.read_write.write_pool).await.unwrap();
assert!(!success);
}
}
3 changes: 1 addition & 2 deletions crates/lakekeeper/src/implementations/postgres/catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,9 @@ impl Catalog for super::PostgresCatalog {

// ---------------- Bootstrap ----------------
async fn bootstrap<'a>(
terms_accepted: bool,
transaction: <Self::Transaction as Transaction<Self::State>>::Transaction<'a>,
) -> Result<bool> {
bootstrap(terms_accepted, &mut **transaction).await
bootstrap(&mut **transaction).await
}

async fn get_warehouse_by_name(
Expand Down
7 changes: 1 addition & 6 deletions crates/lakekeeper/src/serve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,18 +228,13 @@
fn validate_server_info(server_info: &ServerInfo) -> anyhow::Result<()> {
match server_info {
ServerInfo::NotBootstrapped => {
tracing::info!("The catalog is not bootstrapped. Bootstrapping sets the initial administrator. Please open the Web-UI after startup or call the bootstrap endpoint directly.");

Check warning on line 231 in crates/lakekeeper/src/serve.rs

View workflow job for this annotation

GitHub Actions / check-format

Diff in /home/runner/work/lakekeeper/lakekeeper/crates/lakekeeper/src/serve.rs
Ok(())
}
ServerInfo::Bootstrapped {
server_id,
terms_accepted,
} => {
if !terms_accepted {
Err(anyhow!(
"The terms of service have not been accepted on bootstrap."
))
} else if *server_id != CONFIG.server_id {
if *server_id != CONFIG.server_id {
Err(anyhow!(
"The server ID during bootstrap {} does not match the server ID in the configuration {}.",
server_id, CONFIG.server_id
Expand Down
3 changes: 0 additions & 3 deletions crates/lakekeeper/src/service/catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,6 @@ pub enum ServerInfo {
Bootstrapped {
/// Server ID of the catalog at the time of bootstrapping
server_id: uuid::Uuid,
/// Whether the terms have been accepted
terms_accepted: bool,
},
}

Expand Down Expand Up @@ -280,7 +278,6 @@ where
/// If bootstrapped succeeded, return Ok(true).
/// If the catalog is already bootstrapped, return Ok(false).
async fn bootstrap<'a>(
terms_accepted: bool,
transaction: <Self::Transaction as Transaction<Self::State>>::Transaction<'a>,
) -> Result<bool>;

Expand Down
2 changes: 1 addition & 1 deletion crates/lakekeeper/src/tests/drop_warehouse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ async fn test_cannot_drop_warehouse_before_purge_tasks_completed(pool: PgPool) {
ApiServer::bootstrap(
api_context.clone(),
random_request_metadata(),
BootstrapRequest::builder().accept_terms_of_use().build(),
BootstrapRequest::builder().build(),
)
.await
.unwrap();
Expand Down
1 change: 0 additions & 1 deletion crates/lakekeeper/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,6 @@ pub(crate) async fn setup<T: Authorizer>(
api_context.clone(),
metadata.clone(),
BootstrapRequest {
accept_terms_of_use: true,
is_operator: true,
user_name: None,
user_email: None,
Expand Down
5 changes: 0 additions & 5 deletions docs/docs/api/management-open-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2454,12 +2454,7 @@ components:
- azure-system-identity
BootstrapRequest:
type: object
required:
- accept-terms-of-use
properties:
accept-terms-of-use:
type: boolean
description: Set to true if you accept LAKEKEEPER terms of use.
is-operator:
type: boolean
description: |-
Expand Down
5 changes: 1 addition & 4 deletions docs/docs/bootstrap.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ After the initial deployment, Lakekeeper needs to be bootstrapped. This can be d
curl --location 'https://<lakekeeper-url>/management/v1/bootstrap' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <my-bearer-token>' \
--data '{
"accept-terms-of-use": true
}'
--data '{}'
```

`<my-bearer-token>` is obtained by logging into the IdP before bootstrapping Lakekeeper. If authentication is disabled, no token is required. Lakekeeper can only be bootstrapped once.
Expand All @@ -16,7 +14,6 @@ During bootstrapping, Lakekeeper performs the following actions:

* Grants the server's `admin` role to the user performing the POST request. The user is identified by their token. If authentication is disabled, the `Authorization` header is not required, and no `admin` is set, as permissions are disabled in this case.
* Stores the current [Server ID](./concepts.md#server) to prevent unwanted future changes of the Server ID that would break permissions.
* Accepts terms of use as defined by our [License](../../about/license.md).
* If `LAKEKEEPER__ENABLE_DEFAULT_PROJECT` is enabled (default), a default project with the NIL Project ID ("00000000-0000-0000-0000-000000000000") is created.

If the initial user is a technical user (e.g., a Kubernetes Operator) managing the Lakekeeper deployment, the `admin` role might not be sufficient as it limits access to projects until the `admin` grants themselves permission. For technical users, the `operator` role grants full access to all APIs and can be obtained by adding `"is-operator": true` to the JSON body of the bootstrap request.
15 changes: 2 additions & 13 deletions examples/access-control-advanced/notebooks/01-Bootstrap.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -102,18 +102,7 @@
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"response = requests.post(\n",
" url=f\"{MANAGEMENT_URL}/v1/bootstrap\",\n",
" headers={\n",
" \"Authorization\": f\"Bearer {access_token}\"\n",
" },\n",
" json={\n",
" \"accept-terms-of-use\": True,\n",
" },\n",
")\n",
"response.raise_for_status()"
]
"source": "response = requests.post(\n url=f\"{MANAGEMENT_URL}/v1/bootstrap\",\n headers={\n \"Authorization\": f\"Bearer {access_token}\"\n },\n json={},\n)\nresponse.raise_for_status()"
},
{
"cell_type": "markdown",
Expand Down Expand Up @@ -287,4 +276,4 @@
},
"nbformat": 4,
"nbformat_minor": 4
}
}
19 changes: 2 additions & 17 deletions examples/access-control-simple/notebooks/01-Bootstrap.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -107,22 +107,7 @@
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"response = requests.post(\n",
" url=f\"{MANAGEMENT_URL}/v1/bootstrap\",\n",
" headers={\n",
" \"Authorization\": f\"Bearer {access_token}\"\n",
" },\n",
" json={\n",
" \"accept-terms-of-use\": True,\n",
" # Optionally, we can override the name / type of the user:\n",
" # \"user-email\": \"[email protected]\",\n",
" # \"user-name\": \"Roald Amundsen\",\n",
" # \"user-type\": \"human\"\n",
" },\n",
")\n",
"response.raise_for_status()"
]
"source": "response = requests.post(\n url=f\"{MANAGEMENT_URL}/v1/bootstrap\",\n headers={\n \"Authorization\": f\"Bearer {access_token}\"\n },\n json={\n # Optionally, we can override the name / type of the user:\n # \"user-email\": \"[email protected]\",\n # \"user-name\": \"Roald Amundsen\",\n # \"user-type\": \"human\"\n },\n)\nresponse.raise_for_status()"
},
{
"cell_type": "markdown",
Expand Down Expand Up @@ -395,4 +380,4 @@
},
"nbformat": 4,
"nbformat_minor": 4
}
}
2 changes: 1 addition & 1 deletion examples/minimal/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ services:
- "-H"
- "Content-Type: application/json"
- "--data"
- '{"accept-terms-of-use": true}'
- '{}'
- "-o"
- "/dev/null"
# - "--fail-with-body"
Expand Down
2 changes: 1 addition & 1 deletion tests/kube-auth/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

set -eu

curl -f -H "Content-Type: application/json" -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" my-lakekeeper:8181/management/v1/bootstrap -d '{"accept-terms-of-use": true}'
curl -f -H "Content-Type: application/json" -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" my-lakekeeper:8181/management/v1/bootstrap -d '{}'
2 changes: 1 addition & 1 deletion tests/migrations/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ services:
- "-H"
- "Content-Type: application/json"
- "--data"
- '{"accept-terms-of-use": true}'
- '{}'
- "-o"
- "/dev/null"
# - "--fail-with-body"
Expand Down
2 changes: 1 addition & 1 deletion tests/python/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ def server(access_token) -> Server:
response = requests.post(
management_url + "v1/bootstrap",
headers={"Authorization": f"Bearer {access_token}"},
json={"accept-terms-of-use": True},
json={},
)
response.raise_for_status()

Expand Down
Loading