Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
7 changes: 5 additions & 2 deletions admin/docopts.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ You can create namespaces and choose which services to enable.
```text
Usage:
admin adduser <username> <email> <password> [--all] [--redis] [--mongodb] [--minio|--seaweedfs] [--postgres] [--milvus] [--storagequota=<quota>|auto]
admin deleteuser <username>
admin deleteuser <username> [--confirm-namespace-delete]
admin listuser [<username>]
admin sso unbind <username>
admin compact [--ttl=<ttl>|10]
admin usage [--debug]
```
Expand All @@ -39,6 +40,7 @@ Usage:
admin adduser create a new user in OpenServerless with the username, email and password provided
admin deleteuser delete a user from the OpenServerless installation via the username provided
admin listuser list all the secrets of an user (default list all the users)
admin sso unbind remove SSO binding while preserving the OpenServerless namespace
admin compact create a one shot job which executes couchdb compact against all available dbs
admin usage calculates and displays PVC disk usage statistics for bound volumes. Shows Total, Size and Available storage per PVC
```
Expand All @@ -53,6 +55,7 @@ Usage:
--postgres enable postgres
--milvus enable milvus vector db
--storagequota=<quota>
--confirm-namespace-delete explicitly delete a namespace on an SSO-enabled cluster
--ttl=<seconds> modify the job ttl after finished (defaults to 10 seconds)
--debug enable debug logging
```
```
11 changes: 10 additions & 1 deletion admin/opsfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ tasks:
silent: true
cmds:
- |
if test "$SSO_ENABLED" = "true" && ! {{.__confirm_namespace_delete}}
then
echo "SSO is enabled on this cluster."
echo "User administration is delegated to the remote authentication provider."
echo "This command would delete the OpenServerless namespace and its resources."
echo "Use 'ops admin sso unbind {{._username_}}' to disable SSO access while preserving the namespace."
echo "Re-run with '--confirm-namespace-delete' only when you really want to deprovision the namespace."
exit 1
fi
{{.DRY}} kubectl -n nuvolaris delete wsku/{{._username_}}
{{.DRY}} $OPS -config -r {{._username_}}

Expand Down Expand Up @@ -188,4 +197,4 @@ tasks:
echo 'flag is {{.__debug}}'
FLAG=--debug
fi
bun usage/usage.js $FLAG
bun usage/usage.js $FLAG
33 changes: 33 additions & 0 deletions admin/sso/docopts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!---
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
# Tasks `ops admin sso`

Manage SSO bindings for OpenServerless namespaces.

## Synopsis

```text
Usage:
sso unbind <username>
```

## Commands
```
sso unbind disable SSO login for a namespace while preserving its resources
```
38 changes: 38 additions & 0 deletions admin/sso/opsfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

version: "3"

env:
KUBECONFIG:
sh: |
if test -e "$OPS_TMP/kubeconfig"
then echo "$OPS_TMP/kubeconfig"
else echo ~/.kube/config
fi

tasks:

unbind:
desc: disable SSO login for a namespace while preserving resources
silent: true
cmds:
- |
kubectl -n nuvolaris annotate wsku/{{._username_}} openserverless.apache.org/sso-disabled=true --overwrite
echo "SSO login disabled for namespace {{._username_}}."
echo "The OpenServerless namespace and its resources were preserved."
echo "To delete them explicitly, run: ops admin deleteuser {{._username_}} --confirm-namespace-delete"
4 changes: 3 additions & 1 deletion cloud/k3s/docopts.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Usage:
k3s delete <server> [<user>]
k3s info
k3s kubeconfig <server> [<user>]
k3s registry <server> [<user>]
k3s status
```

Expand All @@ -38,5 +39,6 @@ Usage:
delete uninstall k3s with ssh in <server> using <username> with sudo
info info on the server
kubeconfig recover the kubeconfig from a k3s server <server> with user <username>
registry configure the private registry endpoint used by K3s containerd
status status of the server
```
```
12 changes: 12 additions & 0 deletions cloud/k3s/opsfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ tasks:
k3sup install --k3s-version="{{.K3S_VERSION}}"
--host="{{._server_}}" --user="{{.INSTALL_USER}}"
--local-path=$OPS_TMP/kubeconfig
- bun registry.ts "{{._server_}}" "{{.INSTALL_USER}}"


cert-manager:
Expand All @@ -78,10 +79,21 @@ tasks:
desc: create a k3s with ssh in <server> using <username> with sudo
cmds:
- config OPERATOR_CONFIG_KUBE=k3s
- config REGISTRY_CONFIG_PULL_HOSTNAME="{{._server_}}:32000"
- task: install
- task: cert-manager
- cp "$OPS_TMP/kubeconfig" "$OPS_TMP/k3s-{{._server_}}.kubeconfig"

registry:
silent: true
desc: configure the OpenServerless registry endpoint on a K3s node
vars:
INSTALL_USER: '{{._user_ | default "root"}}'
cmds:
- test -n "{{._server_}}" || die "required <server> ip or hostname"
- bun registry.ts "{{._server_}}" "{{.INSTALL_USER}}"
- config REGISTRY_CONFIG_PULL_HOSTNAME="{{._server_}}:32000"

delete:
silent: true
desc: uninstall with ssh in <server> using <username> with sudo
Expand Down
33 changes: 33 additions & 0 deletions cloud/k3s/registry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

import {expect, test} from "bun:test";
import {renderRegistryConfig} from "./registry";

test("renders an explicit HTTP mirror for the K3s node registry", () => {
expect(renderRegistryConfig("192.0.2.10")).toBe(
"# Managed by Apache OpenServerless\n" +
"mirrors:\n" +
" \"192.0.2.10:32000\":\n" +
" endpoint:\n" +
" - \"http://192.0.2.10:32000\"\n",
);
});

test("rejects shell syntax in a K3s host", () => {
expect(() => renderRegistryConfig("host;reboot")).toThrow("invalid K3s server");
});
97 changes: 97 additions & 0 deletions cloud/k3s/registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

const HOST = /^[A-Za-z0-9][A-Za-z0-9.-]*$/;
const USER = /^[A-Za-z0-9_][A-Za-z0-9_.-]*$/;

export function renderRegistryConfig(server: string): string {
if (!HOST.test(server)) throw new Error(`invalid K3s server: ${server}`);
return `# Managed by Apache OpenServerless\nmirrors:\n "${server}:32000":\n endpoint:\n - "http://${server}:32000"\n`;
}

async function capture(args: string[]): Promise<{code: number; stdout: string; stderr: string}> {
const process = Bun.spawn(args, {stdout: "pipe", stderr: "pipe"});
const [stdout, stderr, code] = await Promise.all([
new Response(process.stdout).text(),
new Response(process.stderr).text(),
process.exited,
]);
return {code, stdout, stderr};
}

async function writeRemote(args: string[], content: string): Promise<void> {
const process = Bun.spawn(args, {stdin: "pipe", stdout: "inherit", stderr: "inherit"});
process.stdin.write(content);
process.stdin.end();
const code = await process.exited;
if (code !== 0) throw new Error(`command failed (${code}): ${args.join(" ")}`);
}

async function configure(server: string, user: string): Promise<void> {
if (!USER.test(user)) throw new Error(`invalid SSH user: ${user}`);
const destination = `${user}@${server}`;
const ssh = ["ssh", "-oStrictHostKeyChecking=no", destination];
const expected = renderRegistryConfig(server);
const current = await capture([...ssh, "sudo", "cat", "/etc/rancher/k3s/registries.yaml"]);
if (current.code === 0 && current.stdout === expected) {
console.log(`K3s registry already configured for ${server}:32000`);
return;
}
if (current.code === 0 && current.stdout.trim()) {
throw new Error(
"/etc/rancher/k3s/registries.yaml already contains unmanaged configuration; refusing to overwrite it",
);
}

const exists = await capture([
...ssh, "sudo", "test", "-e", "/etc/rancher/k3s/registries.yaml",
]);
if (exists.code === 0) {
throw new Error("cannot read the existing /etc/rancher/k3s/registries.yaml");
}

const directory = await capture([...ssh, "sudo", "mkdir", "-p", "/etc/rancher/k3s"]);
if (directory.code !== 0) throw new Error(directory.stderr || "cannot create K3s config directory");
await writeRemote(
[...ssh, "sudo", "tee", "/etc/rancher/k3s/registries.yaml"],
expected,
);
const restart = await capture([...ssh, "sudo", "systemctl", "restart", "k3s"]);
if (restart.code !== 0) throw new Error(restart.stderr || "cannot restart K3s");

for (let attempt = 0; attempt < 60; attempt += 1) {
const active = await capture([...ssh, "sudo", "systemctl", "is-active", "k3s"]);
if (active.code === 0 && active.stdout.trim() === "active") {
console.log(`K3s registry configured for ${server}:32000`);
return;
}
await Bun.sleep(1000);
}
throw new Error("K3s did not become active after registry configuration");
}

if (import.meta.main) {
const args = Bun.argv.slice(2);
if (args[0] === "--render") {
console.log(renderRegistryConfig(args[1]));
} else {
const server = args[0];
const user = args[1] || "root";
if (!server) throw new Error("K3s server is required");
await configure(server, user);
}
}
2 changes: 2 additions & 0 deletions config/docopts.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Configure OpenServerless
Usage:
config (enable|disable) [--all] [--redis] [--mongodb] [--minio] [--cron] [--static] [--postgres] [--prometheus] [--slack] [--mail] [--affinity] [--tolerations] [--quota] [--milvus] [--registry] [--seaweedfs]
config apihost (<apihost>|auto) [--tls=<email>] [--protocol=<http/https>|auto]
config sso
config runtimes [<runtimesjson>]
config slack [--apiurl=<slackapiurl>] [--channel=<slackchannel>]
config mail [--mailuser=<mailuser>] [--mailpwd=<mailpwd>] [--mailfrom=<mailfrom>] [--mailto=<mailto>]
Expand Down Expand Up @@ -58,6 +59,7 @@ Usage:

```
config apihost configure the apihost (auto: auto assign) and enable tls
config sso configure SSO/OIDC integration for admin-api; run `ops config sso --help` for details
config runtime show the current runtime.json or import the <runtime-json> if provided
config enable enable OpenServerless services to install
config disable disable OpenServerless services to install
Expand Down
79 changes: 79 additions & 0 deletions config/sso/docopts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<!---
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->

# Tasks `ops config sso`

Configure OpenServerless SSO/OIDC integration for admin-api.

## Synopsis

```text
Usage:
sso keycloak --enable --issuer-url=<issuer-url> --jwks-url=<jwks-url> (--audience=<audience>|--client-id=<client-id>) --required-group=<group> [--client-secret=<client-secret>] [--username-claim=<claim>] [--groups-claim=<claim>] [--namespace=<namespace>] [--configmap=<name>] [--secret=<name>] [--statefulset=<name>] [--container=<name>] [--no-rollout]
sso show
sso disable [--namespace=<namespace>] [--configmap=<name>] [--secret=<name>] [--statefulset=<name>] [--container=<name>] [--no-rollout]
```

## Managed resources

The task owns a dedicated ConfigMap, named
`openserverless-sso-config` by default, with these keys:

- `OIDC_ISSUER_URL`
- `OIDC_JWKS_URL`
- `OIDC_AUDIENCE`
- `OIDC_CLIENT_ID`
- `OIDC_REQUIRED_GROUP`
- `OIDC_USERNAME_CLAIM`
- `OIDC_GROUPS_CLAIM`
- `SSO_AUTOPROVISION_ON_LOGIN`
- `SSO_AUTOPROVISION_TIMEOUT_SECONDS`
- `SSO_AUTOPROVISION_POLL_SECONDS`
- `SSO_AUTOPROVISION_DEFAULT_SERVICES`
- `SSO_NAMESPACE_PRESERVE_VALID`
- `SSO_NAMESPACE_HASH_LENGTH`
- `SSO_NAMESPACE_MAX_LENGTH`

With `--client-secret`, the task also owns a dedicated Secret, named
`openserverless-sso-secret` by default, containing only
`OIDC_CLIENT_SECRET`.

The selected admin-api container receives exact, prefix-free `envFrom`
references to those resources. The task does not manage direct `env` entries,
other `envFrom` references, volumes, volume mounts, or annotations.

`disable` removes only the managed references and dedicated resources. Missing
resources are not errors. Repeated disable is a no-op for the StatefulSet.
Kubernetes automatically rolls out a changed pod template; `--no-rollout`
skips waiting for it and never issues an additional restart.

## Options

```text
--username-claim=<claim> OIDC username claim [default: preferred_username]
--groups-claim=<claim> OIDC groups claim [default: groups]
--client-id=<client-id> OIDC client id; defaults to audience
--client-secret=<secret> confidential client secret stored only in Kubernetes
--namespace=<namespace> Kubernetes namespace [default: nuvolaris]
--configmap=<name> ConfigMap name [default: openserverless-sso-config]
--secret=<name> Secret name [default: openserverless-sso-secret]
--statefulset=<name> admin-api StatefulSet [default: nuvolaris-system-api]
--container=<name> admin-api container [default: nuvolaris-system-api]
--no-rollout do not restart or wait for admin-api rollout
```
Loading