mirror of
https://github.com/ClusterCockpit/cc-backend
synced 2026-06-18 01:17:29 +02:00
Merge pull request #561 from ClusterCockpit/feature-283-remove-env-support-alt
feat(auth): replace .env/godotenv secret handling with config-based s…
This commit is contained in:
12
CLAUDE.md
12
CLAUDE.md
@@ -161,9 +161,15 @@ applied automatically on startup. Version tracking in `version` table.
|
|||||||
- `username`: Authentication username (optional)
|
- `username`: Authentication username (optional)
|
||||||
- `password`: Authentication password (optional)
|
- `password`: Authentication password (optional)
|
||||||
- `creds-file-path`: Path to NATS credentials file (optional)
|
- `creds-file-path`: Path to NATS credentials file (optional)
|
||||||
- **.env**: Environment variables (secrets like JWT keys)
|
- **Secrets** (JWT keys, LDAP sync password, OIDC client id/secret, cross-login
|
||||||
- Copy from `configs/env-template.txt`
|
keys): configured directly in `config.json` under the `auth` section where they
|
||||||
- NEVER commit this file
|
are used (e.g. `auth.jwts.public-key`, `auth.jwts.private-key`,
|
||||||
|
`auth.ldap.sync-password`, `auth.oidc.client-id`/`client-secret`).
|
||||||
|
- Each secret may also be supplied via its environment variable
|
||||||
|
(`JWT_PUBLIC_KEY`, `JWT_PRIVATE_KEY`, `LDAP_ADMIN_PASSWORD`, `OID_CLIENT_ID`,
|
||||||
|
`OID_CLIENT_SECRET`, `CROSS_LOGIN_JWT_PUBLIC_KEY`, `CROSS_LOGIN_JWT_HS512_KEY`).
|
||||||
|
- The environment variable takes precedence over the value in `config.json`.
|
||||||
|
- The former `.env`/godotenv mechanism has been removed.
|
||||||
- **cluster.json**: Cluster topology and metric definitions (loaded from archive or config)
|
- **cluster.json**: Cluster topology and metric definitions (loaded from archive or config)
|
||||||
|
|
||||||
## Database
|
## Database
|
||||||
|
|||||||
19
README.md
19
README.md
@@ -129,12 +129,11 @@ git clone https://github.com/ClusterCockpit/cc-backend.git
|
|||||||
cd ./cc-backend/
|
cd ./cc-backend/
|
||||||
make
|
make
|
||||||
|
|
||||||
# EDIT THE .env FILE BEFORE YOU DEPLOY (Change the secrets)!
|
|
||||||
# If authentication is disabled, it can be empty.
|
|
||||||
cp configs/env-template.txt .env
|
|
||||||
vim .env
|
|
||||||
|
|
||||||
cp configs/config.json .
|
cp configs/config.json .
|
||||||
|
# EDIT config.json BEFORE YOU DEPLOY: change the secrets under "auth.jwts"
|
||||||
|
# ("public-key"/"private-key"). Each secret can also be supplied via an
|
||||||
|
# environment variable (e.g. JWT_PUBLIC_KEY), which takes precedence over the
|
||||||
|
# value in config.json.
|
||||||
vim config.json
|
vim config.json
|
||||||
|
|
||||||
#Optional: Link an existing job archive:
|
#Optional: Link an existing job archive:
|
||||||
@@ -157,8 +156,14 @@ ln -s <your-existing-job-archive> ./var/job-archive
|
|||||||
Browser sessions are stored server-side in the SQLite database (the `sessions`
|
Browser sessions are stored server-side in the SQLite database (the `sessions`
|
||||||
table) using [`alexedwards/scs`](https://github.com/alexedwards/scs); only an
|
table) using [`alexedwards/scs`](https://github.com/alexedwards/scs); only an
|
||||||
opaque random token is kept in the session cookie. No cookie-signing secret is
|
opaque random token is kept in the session cookie. No cookie-signing secret is
|
||||||
required, so the former `SESSION_KEY` environment variable is no longer used and
|
required, so the former `SESSION_KEY` environment variable is no longer used.
|
||||||
can be removed from your `.env`.
|
|
||||||
|
Secrets (JWT keys, LDAP sync password, OIDC client id/secret, cross-login keys)
|
||||||
|
are configured directly in `config.json` under the `auth` section. Each secret
|
||||||
|
may also be supplied via its environment variable (e.g. `JWT_PUBLIC_KEY`,
|
||||||
|
`JWT_PRIVATE_KEY`, `LDAP_ADMIN_PASSWORD`, `OID_CLIENT_ID`, `OID_CLIENT_SECRET`,
|
||||||
|
`CROSS_LOGIN_JWT_PUBLIC_KEY`, `CROSS_LOGIN_JWT_HS512_KEY`); the environment
|
||||||
|
variable takes precedence when set. The previous `.env` file is no longer used.
|
||||||
|
|
||||||
The session cookie's `Secure` flag is set automatically when cc-backend serves
|
The session cookie's `Secure` flag is set automatically when cc-backend serves
|
||||||
HTTPS itself (i.e. `https-cert-file` and `https-key-file` are configured in
|
HTTPS itself (i.e. `https-cert-file` and `https-key-file` are configured in
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func cliInit() {
|
func cliInit() {
|
||||||
flag.BoolVar(&flagInit, "init", false, "Setup var directory, initialize sqlite database file, config.json and .env")
|
flag.BoolVar(&flagInit, "init", false, "Setup var directory, initialize sqlite database file and config.json")
|
||||||
flag.BoolVar(&flagReinitDB, "init-db", false, "Go through job-archive and re-initialize the 'job', 'tag', and 'jobtag' tables (all running jobs will be lost!)")
|
flag.BoolVar(&flagReinitDB, "init-db", false, "Go through job-archive and re-initialize the 'job', 'tag', and 'jobtag' tables (all running jobs will be lost!)")
|
||||||
flag.BoolVar(&flagSyncLDAP, "sync-ldap", false, "Sync the 'hpc_user' table with ldap")
|
flag.BoolVar(&flagSyncLDAP, "sync-ldap", false, "Sync the 'hpc_user' table with ldap")
|
||||||
flag.BoolVar(&flagServer, "server", false, "Start a server, continues listening on port after initialization and argument handling")
|
flag.BoolVar(&flagServer, "server", false, "Start a server, continues listening on port after initialization and argument handling")
|
||||||
|
|||||||
@@ -18,13 +18,6 @@ import (
|
|||||||
"github.com/ClusterCockpit/cc-lib/v2/util"
|
"github.com/ClusterCockpit/cc-lib/v2/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const envString = `
|
|
||||||
# Base64 encoded Ed25519 keys (DO NOT USE THESE TWO IN PRODUCTION!)
|
|
||||||
# You can generate your own keypair using the gen-keypair tool
|
|
||||||
JWT_PUBLIC_KEY="kzfYrYy+TzpanWZHJ5qSdMj5uKUWgq74BWhQG6copP0="
|
|
||||||
JWT_PRIVATE_KEY="dtPC/6dWJFKZK7KZ78CvWuynylOmjBFyMsUWArwmodOTN9itjL5POlqdZkcnmpJ0yPm4pRaCrvgFaFAbpyik/Q=="
|
|
||||||
`
|
|
||||||
|
|
||||||
const configString = `
|
const configString = `
|
||||||
{
|
{
|
||||||
"main": {
|
"main": {
|
||||||
@@ -54,7 +47,9 @@ const configString = `
|
|||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"jwts": {
|
"jwts": {
|
||||||
"max-age": "2000h"
|
"max-age": "2000h",
|
||||||
|
"public-key": "kzfYrYy+TzpanWZHJ5qSdMj5uKUWgq74BWhQG6copP0=",
|
||||||
|
"private-key": "dtPC/6dWJFKZK7KZ78CvWuynylOmjBFyMsUWArwmodOTN9itjL5POlqdZkcnmpJ0yPm4pRaCrvgFaFAbpyik/Q=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,10 +64,6 @@ func initEnv() {
|
|||||||
cclog.Abortf("Could not write default ./config.json with permissions '0o666'. Application initialization failed, exited.\nError: %s\n", err.Error())
|
cclog.Abortf("Could not write default ./config.json with permissions '0o666'. Application initialization failed, exited.\nError: %s\n", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.WriteFile(".env", []byte(envString), 0o666); err != nil {
|
|
||||||
cclog.Abortf("Could not write default ./.env file with permissions '0o666'. Application initialization failed, exited.\nError: %s\n", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.Mkdir("var", 0o777); err != nil {
|
if err := os.Mkdir("var", 0o777); err != nil {
|
||||||
cclog.Abortf("Could not create default ./var folder with permissions '0o777'. Application initialization failed, exited.\nError: %s\n", err.Error())
|
cclog.Abortf("Could not create default ./var folder with permissions '0o777'. Application initialization failed, exited.\nError: %s\n", err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import (
|
|||||||
"github.com/ClusterCockpit/cc-lib/v2/schema"
|
"github.com/ClusterCockpit/cc-lib/v2/schema"
|
||||||
"github.com/ClusterCockpit/cc-lib/v2/util"
|
"github.com/ClusterCockpit/cc-lib/v2/util"
|
||||||
"github.com/google/gops/agent"
|
"github.com/google/gops/agent"
|
||||||
"github.com/joho/godotenv"
|
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
)
|
)
|
||||||
@@ -89,13 +88,6 @@ func initGops() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadEnvironment() error {
|
|
||||||
if err := godotenv.Load(); err != nil {
|
|
||||||
return fmt.Errorf("loading .env file: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func initConfiguration() error {
|
func initConfiguration() error {
|
||||||
ccconf.Init(flagConfigFile)
|
ccconf.Init(flagConfigFile)
|
||||||
|
|
||||||
@@ -224,7 +216,14 @@ func checkDefaultSecurityKeys() {
|
|||||||
// Default JWT public key from init.go
|
// Default JWT public key from init.go
|
||||||
defaultJWTPublic := "kzfYrYy+TzpanWZHJ5qSdMj5uKUWgq74BWhQG6copP0="
|
defaultJWTPublic := "kzfYrYy+TzpanWZHJ5qSdMj5uKUWgq74BWhQG6copP0="
|
||||||
|
|
||||||
if os.Getenv("JWT_PUBLIC_KEY") == defaultJWTPublic {
|
// Resolve the public key the same way the authenticators do: environment
|
||||||
|
// variable takes precedence over the value configured in config.json.
|
||||||
|
pubKey := os.Getenv("JWT_PUBLIC_KEY")
|
||||||
|
if pubKey == "" && auth.Keys.JwtConfig != nil {
|
||||||
|
pubKey = auth.Keys.JwtConfig.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
if pubKey == defaultJWTPublic {
|
||||||
cclog.Warn("Using default JWT keys - not recommended for production environments")
|
cclog.Warn("Using default JWT keys - not recommended for production environments")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -495,7 +494,7 @@ func run() error {
|
|||||||
if flagInit {
|
if flagInit {
|
||||||
initEnv()
|
initEnv()
|
||||||
cclog.Exit("Successfully setup environment!\n" +
|
cclog.Exit("Successfully setup environment!\n" +
|
||||||
"Please review config.json and .env and adjust it to your needs.\n" +
|
"Please review config.json and adjust it to your needs.\n" +
|
||||||
"Add your job-archive at ./var/job-archive.")
|
"Add your job-archive at ./var/job-archive.")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,17 +504,12 @@ func run() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize subsystems in dependency order:
|
// Initialize subsystems in dependency order:
|
||||||
// 1. Load environment variables from .env file (contains sensitive configuration)
|
// 1. Load configuration from config.json (secrets live in config; individual
|
||||||
// 2. Load configuration from config.json (may reference environment variables)
|
// secrets may be overridden via environment variables)
|
||||||
// 3. Handle database migration commands if requested
|
// 2. Handle database migration commands if requested
|
||||||
// 4. Initialize database connection (requires config for connection string)
|
// 3. Initialize database connection (requires config for connection string)
|
||||||
// 5. Handle user commands if requested (requires database and authentication config)
|
// 4. Handle user commands if requested (requires database and authentication config)
|
||||||
// 6. Initialize subsystems like archive and metrics (require config and database)
|
// 5. Initialize subsystems like archive and metrics (require config and database)
|
||||||
|
|
||||||
// Load environment and configuration
|
|
||||||
if err := loadEnvironment(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := initConfiguration(); err != nil {
|
if err := initConfiguration(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -9,7 +9,9 @@
|
|||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"jwts": {
|
"jwts": {
|
||||||
"max-age": "2000h"
|
"max-age": "2000h",
|
||||||
|
"public-key": "kzfYrYy+TzpanWZHJ5qSdMj5uKUWgq74BWhQG6copP0=",
|
||||||
|
"private-key": "dtPC/6dWJFKZK7KZ78CvWuynylOmjBFyMsUWArwmodOTN9itjL5POlqdZkcnmpJ0yPm4pRaCrvgFaFAbpyik/Q=="
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"metric-store-external": [
|
"metric-store-external": [
|
||||||
|
|||||||
@@ -30,7 +30,9 @@
|
|||||||
},
|
},
|
||||||
"auth": {
|
"auth": {
|
||||||
"jwts": {
|
"jwts": {
|
||||||
"max-age": "2000h"
|
"max-age": "2000h",
|
||||||
|
"public-key": "kzfYrYy+TzpanWZHJ5qSdMj5uKUWgq74BWhQG6copP0=",
|
||||||
|
"private-key": "dtPC/6dWJFKZK7KZ78CvWuynylOmjBFyMsUWArwmodOTN9itjL5POlqdZkcnmpJ0yPm4pRaCrvgFaFAbpyik/Q=="
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cron": {
|
"cron": {
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
# Base64 encoded Ed25519 keys (DO NOT USE THESE TWO IN PRODUCTION!)
|
|
||||||
# You can generate your own keypair using `go run tools/gen-keypair/main.go`
|
|
||||||
JWT_PUBLIC_KEY="kzfYrYy+TzpanWZHJ5qSdMj5uKUWgq74BWhQG6copP0="
|
|
||||||
JWT_PRIVATE_KEY="dtPC/6dWJFKZK7KZ78CvWuynylOmjBFyMsUWArwmodOTN9itjL5POlqdZkcnmpJ0yPm4pRaCrvgFaFAbpyik/Q=="
|
|
||||||
|
|
||||||
# Base64 encoded Ed25519 public key for accepting externally generated JWTs
|
|
||||||
# Keys in PEM format can be converted, see `tools/convert-pem-pubkey/Readme.md`
|
|
||||||
CROSS_LOGIN_JWT_PUBLIC_KEY=""
|
|
||||||
|
|
||||||
# Password for the ldap server (optional)
|
|
||||||
LDAP_ADMIN_PASSWORD="mashup"
|
|
||||||
1
go.mod
1
go.mod
@@ -28,7 +28,6 @@ require (
|
|||||||
github.com/golang-migrate/migrate/v4 v4.19.1
|
github.com/golang-migrate/migrate/v4 v4.19.1
|
||||||
github.com/google/gops v0.3.29
|
github.com/google/gops v0.3.29
|
||||||
github.com/jmoiron/sqlx v1.4.0
|
github.com/jmoiron/sqlx v1.4.0
|
||||||
github.com/joho/godotenv v1.5.1
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.44
|
github.com/mattn/go-sqlite3 v1.14.44
|
||||||
github.com/parquet-go/parquet-go v0.30.1
|
github.com/parquet-go/parquet-go v0.30.1
|
||||||
github.com/qustavo/sqlhooks/v2 v2.1.0
|
github.com/qustavo/sqlhooks/v2 v2.1.0
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -191,8 +191,6 @@ github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZ
|
|||||||
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
|
||||||
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||||
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
|
||||||
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
||||||
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||||
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
# How to run `cc-backend` as a systemd service.
|
# How to run `cc-backend` as a systemd service
|
||||||
|
|
||||||
The files in this directory assume that you install ClusterCockpit to
|
The files in this directory assume that you install ClusterCockpit to
|
||||||
`/opt/monitoring/cc-backend`.
|
`/opt/monitoring/cc-backend`.
|
||||||
Of course you can choose any other location, but make sure you replace all paths
|
Of course you can choose any other location, but make sure you replace all paths
|
||||||
starting with `/opt/monitoring/cc-backend` in the `clustercockpit.service` file!
|
starting with `/opt/monitoring/cc-backend` in the `clustercockpit.service` file!
|
||||||
|
|
||||||
The `config.json` may contain the optional fields *user* and *group*. If
|
The `config.json` may contain the optional fields _user_ and _group_. If
|
||||||
specified, the application will call
|
specified, the application will call
|
||||||
[setuid](https://man7.org/linux/man-pages/man2/setuid.2.html) and
|
[setuid](https://man7.org/linux/man-pages/man2/setuid.2.html) and
|
||||||
[setgid](https://man7.org/linux/man-pages/man2/setgid.2.html) after reading the
|
[setgid](https://man7.org/linux/man-pages/man2/setgid.2.html) after reading the
|
||||||
config file and binding to a TCP port (so it can take a privileged port), but
|
config file and binding to a TCP port (so it can take a privileged port), but
|
||||||
before it starts accepting any connections. This is good for security, but also
|
before it starts accepting any connections. This is good for security, but also
|
||||||
means that the `var/` directory must be readable and writeable by this user.
|
means that the `var/` directory must be readable and writeable by this user. The
|
||||||
The `.env` and `config.json` files may contain secrets and should not be
|
`config.json` file may contain secrets. If this file is changed, the server must
|
||||||
readable by this user. If these files are changed, the server must be restarted.
|
be restarted.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# 1. Clone this repository somewhere in your home
|
# 1. Clone this repository somewhere in your home
|
||||||
@@ -25,11 +25,9 @@ make
|
|||||||
sudo mkdir -p /opt/monitoring/cc-backend/
|
sudo mkdir -p /opt/monitoring/cc-backend/
|
||||||
cp ./cc-backend /opt/monitoring/cc-backend/
|
cp ./cc-backend /opt/monitoring/cc-backend/
|
||||||
|
|
||||||
# 3. Modify the `./config.json` and env-template.txt file from the configs directory to your liking and put it in the target directory
|
# 3. Modify the `./config.json` file from the configs directory to your liking and put it in the target directory
|
||||||
cp ./configs/config.json /opt/monitoring/config.json
|
cp ./configs/config.json /opt/monitoring/config.json
|
||||||
cp ./configs/env-template.txt /opt/monitoring/.env
|
vim /opt/monitoring/config.json # do your thing (including the secrets under "auth")...
|
||||||
vim /opt/monitoring/config.json # do your thing...
|
|
||||||
vim /opt/monitoring/.env # do your thing...
|
|
||||||
|
|
||||||
# 4. (Optional) Customization: Add your versions of the login view, legal texts, and logo image.
|
# 4. (Optional) Customization: Add your versions of the login view, legal texts, and logo image.
|
||||||
# You may use the templates in `./web/templates` as blueprint. Every overwrite separate.
|
# You may use the templates in `./web/templates` as blueprint. Every overwrite separate.
|
||||||
@@ -57,8 +55,9 @@ It is recommended to install all ClusterCockpit components in a common directory
|
|||||||
In the following we use `/opt/monitoring`.
|
In the following we use `/opt/monitoring`.
|
||||||
|
|
||||||
Two systemd services run on the central monitoring server:
|
Two systemd services run on the central monitoring server:
|
||||||
* clustercockpit : binary cc-backend in `/opt/monitoring/cc-backend`.
|
|
||||||
* cc-metric-store : Binary cc-metric-store in `/opt/monitoring/cc-metric-store`.
|
- clustercockpit : binary cc-backend in `/opt/monitoring/cc-backend`.
|
||||||
|
- cc-metric-store : Binary cc-metric-store in `/opt/monitoring/cc-metric-store`.
|
||||||
|
|
||||||
ClusterCockpit is deployed as a single binary that embeds all static assets.
|
ClusterCockpit is deployed as a single binary that embeds all static assets.
|
||||||
We recommend keeping all `cc-backend` binary versions in a folder `archive` and
|
We recommend keeping all `cc-backend` binary versions in a folder `archive` and
|
||||||
@@ -68,12 +67,13 @@ This allows for easy roll-back in case something doesn't work.
|
|||||||
## Workflow to deploy new version
|
## Workflow to deploy new version
|
||||||
|
|
||||||
This example assumes the DB and job archive versions did not change.
|
This example assumes the DB and job archive versions did not change.
|
||||||
* Stop systemd service: `$ sudo systemctl stop clustercockpit.service`
|
|
||||||
* Backup the sqlite DB file and Job archive directory tree!
|
- Stop systemd service: `$ sudo systemctl stop clustercockpit.service`
|
||||||
* Copy `cc-backend` binary to `/opt/monitoring/cc-backend/archive` (Tip: Use a
|
- Backup the sqlite DB file and Job archive directory tree!
|
||||||
date tag like `YYYYMMDD-cc-backend`)
|
- Copy `cc-backend` binary to `/opt/monitoring/cc-backend/archive` (Tip: Use a
|
||||||
* Link from cc-backend root to current version
|
date tag like `YYYYMMDD-cc-backend`)
|
||||||
* Start systemd service: `$ sudo systemctl start clustercockpit.service`
|
- Link from cc-backend root to current version
|
||||||
* Check if everything is ok: `$ sudo systemctl status clustercockpit.service`
|
- Start systemd service: `$ sudo systemctl start clustercockpit.service`
|
||||||
* Check log for issues: `$ sudo journalctl -u clustercockpit.service`
|
- Check if everything is ok: `$ sudo systemctl status clustercockpit.service`
|
||||||
* Check the ClusterCockpit web frontend and your Slurm adapters if anything is broken!
|
- Check log for issues: `$ sudo journalctl -u clustercockpit.service`
|
||||||
|
- Check the ClusterCockpit web frontend and your Slurm adapters if anything is broken!
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -115,6 +116,18 @@ type AuthConfig struct {
|
|||||||
// Keys holds the global authentication configuration
|
// Keys holds the global authentication configuration
|
||||||
var Keys AuthConfig
|
var Keys AuthConfig
|
||||||
|
|
||||||
|
// secretFromEnv resolves a secret from the environment or config. The
|
||||||
|
// environment variable takes precedence when set and non-empty; otherwise the
|
||||||
|
// value configured in config.json is used. This lets deployments inject secrets
|
||||||
|
// via the environment (or a secret manager) while keeping config.json
|
||||||
|
// self-contained for simple setups.
|
||||||
|
func secretFromEnv(envVar, configValue string) string {
|
||||||
|
if v := os.Getenv(envVar); v != "" {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return configValue
|
||||||
|
}
|
||||||
|
|
||||||
// Authentication manages all authentication methods and session handling
|
// Authentication manages all authentication methods and session handling
|
||||||
type Authentication struct {
|
type Authentication struct {
|
||||||
sessionManager *scs.SessionManager
|
sessionManager *scs.SessionManager
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -39,6 +38,23 @@ type JWTAuthConfig struct {
|
|||||||
|
|
||||||
// Should an existent user be updated in the DB based on the information in the token
|
// Should an existent user be updated in the DB based on the information in the token
|
||||||
UpdateUserOnLogin bool `json:"update-user-on-login"`
|
UpdateUserOnLogin bool `json:"update-user-on-login"`
|
||||||
|
|
||||||
|
// Base64 encoded Ed25519 public key used to validate JWTs.
|
||||||
|
// Overridden by the JWT_PUBLIC_KEY environment variable when set.
|
||||||
|
PublicKey string `json:"public-key"`
|
||||||
|
|
||||||
|
// Base64 encoded Ed25519 private key used to sign JWTs.
|
||||||
|
// Overridden by the JWT_PRIVATE_KEY environment variable when set.
|
||||||
|
PrivateKey string `json:"private-key"`
|
||||||
|
|
||||||
|
// Base64 encoded Ed25519 public key for accepting externally generated JWTs.
|
||||||
|
// Overridden by the CROSS_LOGIN_JWT_PUBLIC_KEY environment variable when set.
|
||||||
|
CrossLoginPublicKey string `json:"cross-login-public-key"`
|
||||||
|
|
||||||
|
// Base64 encoded HMAC (HS256/HS512) key for accepting externally generated
|
||||||
|
// session login tokens.
|
||||||
|
// Overridden by the CROSS_LOGIN_JWT_HS512_KEY environment variable when set.
|
||||||
|
CrossLoginHS512Key string `json:"cross-login-hs512-key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type JWTAuthenticator struct {
|
type JWTAuthenticator struct {
|
||||||
@@ -47,9 +63,10 @@ type JWTAuthenticator struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ja *JWTAuthenticator) Init() error {
|
func (ja *JWTAuthenticator) Init() error {
|
||||||
pubKey, privKey := os.Getenv("JWT_PUBLIC_KEY"), os.Getenv("JWT_PRIVATE_KEY")
|
pubKey := secretFromEnv("JWT_PUBLIC_KEY", Keys.JwtConfig.PublicKey)
|
||||||
|
privKey := secretFromEnv("JWT_PRIVATE_KEY", Keys.JwtConfig.PrivateKey)
|
||||||
if pubKey == "" || privKey == "" {
|
if pubKey == "" || privKey == "" {
|
||||||
cclog.Warn("environment variables 'JWT_PUBLIC_KEY' or 'JWT_PRIVATE_KEY' not set (token based authentication will not work)")
|
cclog.Warn("JWT public/private key not configured ('public-key'/'private-key' in config or 'JWT_PUBLIC_KEY'/'JWT_PRIVATE_KEY' env): token based authentication will not work")
|
||||||
} else {
|
} else {
|
||||||
bytes, err := base64.StdEncoding.DecodeString(pubKey)
|
bytes, err := base64.StdEncoding.DecodeString(pubKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -121,7 +138,7 @@ func (ja *JWTAuthenticator) AuthViaJWT(
|
|||||||
// ProvideJWT generates a new JWT that can be used for authentication
|
// ProvideJWT generates a new JWT that can be used for authentication
|
||||||
func (ja *JWTAuthenticator) ProvideJWT(user *schema.User) (string, error) {
|
func (ja *JWTAuthenticator) ProvideJWT(user *schema.User) (string, error) {
|
||||||
if ja.privateKey == nil {
|
if ja.privateKey == nil {
|
||||||
return "", errors.New("environment variable 'JWT_PRIVATE_KEY' not set")
|
return "", errors.New("JWT private key not configured ('private-key' in config or 'JWT_PRIVATE_KEY' env)")
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
|
|
||||||
cclog "github.com/ClusterCockpit/cc-lib/v2/ccLogger"
|
cclog "github.com/ClusterCockpit/cc-lib/v2/ccLogger"
|
||||||
"github.com/ClusterCockpit/cc-lib/v2/schema"
|
"github.com/ClusterCockpit/cc-lib/v2/schema"
|
||||||
@@ -27,10 +26,11 @@ type JWTCookieSessionAuthenticator struct {
|
|||||||
var _ Authenticator = (*JWTCookieSessionAuthenticator)(nil)
|
var _ Authenticator = (*JWTCookieSessionAuthenticator)(nil)
|
||||||
|
|
||||||
func (ja *JWTCookieSessionAuthenticator) Init() error {
|
func (ja *JWTCookieSessionAuthenticator) Init() error {
|
||||||
pubKey, privKey := os.Getenv("JWT_PUBLIC_KEY"), os.Getenv("JWT_PRIVATE_KEY")
|
pubKey := secretFromEnv("JWT_PUBLIC_KEY", Keys.JwtConfig.PublicKey)
|
||||||
|
privKey := secretFromEnv("JWT_PRIVATE_KEY", Keys.JwtConfig.PrivateKey)
|
||||||
if pubKey == "" || privKey == "" {
|
if pubKey == "" || privKey == "" {
|
||||||
cclog.Warn("environment variables 'JWT_PUBLIC_KEY' or 'JWT_PRIVATE_KEY' not set (token based authentication will not work)")
|
cclog.Warn("JWT public/private key not configured ('public-key'/'private-key' in config or 'JWT_PUBLIC_KEY'/'JWT_PRIVATE_KEY' env): token based authentication will not work")
|
||||||
return errors.New("environment variables 'JWT_PUBLIC_KEY' or 'JWT_PRIVATE_KEY' not set (token based authentication will not work)")
|
return errors.New("JWT public/private key not configured: token based authentication will not work")
|
||||||
} else {
|
} else {
|
||||||
bytes, err := base64.StdEncoding.DecodeString(pubKey)
|
bytes, err := base64.StdEncoding.DecodeString(pubKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -47,8 +47,8 @@ func (ja *JWTCookieSessionAuthenticator) Init() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Look for external public keys
|
// Look for external public keys
|
||||||
pubKeyCrossLogin, keyFound := os.LookupEnv("CROSS_LOGIN_JWT_PUBLIC_KEY")
|
pubKeyCrossLogin := secretFromEnv("CROSS_LOGIN_JWT_PUBLIC_KEY", Keys.JwtConfig.CrossLoginPublicKey)
|
||||||
if keyFound && pubKeyCrossLogin != "" {
|
if pubKeyCrossLogin != "" {
|
||||||
bytes, err := base64.StdEncoding.DecodeString(pubKeyCrossLogin)
|
bytes, err := base64.StdEncoding.DecodeString(pubKeyCrossLogin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Warn("Could not decode cross login JWT public key")
|
cclog.Warn("Could not decode cross login JWT public key")
|
||||||
@@ -57,8 +57,8 @@ func (ja *JWTCookieSessionAuthenticator) Init() error {
|
|||||||
ja.publicKeyCrossLogin = ed25519.PublicKey(bytes)
|
ja.publicKeyCrossLogin = ed25519.PublicKey(bytes)
|
||||||
} else {
|
} else {
|
||||||
ja.publicKeyCrossLogin = nil
|
ja.publicKeyCrossLogin = nil
|
||||||
cclog.Debug("environment variable 'CROSS_LOGIN_JWT_PUBLIC_KEY' not set (cross login token based authentication will not work)")
|
cclog.Debug("cross login JWT public key not configured ('cross-login-public-key' in config or 'CROSS_LOGIN_JWT_PUBLIC_KEY' env): cross login token based authentication will not work")
|
||||||
return errors.New("environment variable 'CROSS_LOGIN_JWT_PUBLIC_KEY' not set (cross login token based authentication will not work)")
|
return errors.New("cross login JWT public key not configured: cross login token based authentication will not work")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warn if other necessary settings are not configured
|
// Warn if other necessary settings are not configured
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
cclog "github.com/ClusterCockpit/cc-lib/v2/ccLogger"
|
cclog "github.com/ClusterCockpit/cc-lib/v2/ccLogger"
|
||||||
@@ -25,12 +24,12 @@ type JWTSessionAuthenticator struct {
|
|||||||
var _ Authenticator = (*JWTSessionAuthenticator)(nil)
|
var _ Authenticator = (*JWTSessionAuthenticator)(nil)
|
||||||
|
|
||||||
func (ja *JWTSessionAuthenticator) Init() error {
|
func (ja *JWTSessionAuthenticator) Init() error {
|
||||||
pubKey := os.Getenv("CROSS_LOGIN_JWT_HS512_KEY")
|
pubKey := secretFromEnv("CROSS_LOGIN_JWT_HS512_KEY", Keys.JwtConfig.CrossLoginHS512Key)
|
||||||
if pubKey == "" {
|
if pubKey == "" {
|
||||||
// Without a configured key the HMAC verification below would run against
|
// Without a configured key the HMAC verification below would run against
|
||||||
// an empty key, which lets anyone forge a valid token. Refuse to register
|
// an empty key, which lets anyone forge a valid token. Refuse to register
|
||||||
// the authenticator in that case so JWT session login is simply disabled.
|
// the authenticator in that case so JWT session login is simply disabled.
|
||||||
return errors.New("CROSS_LOGIN_JWT_HS512_KEY not set: JWT session login disabled")
|
return errors.New("cross login HS512 key not configured ('cross-login-hs512-key' in config or 'CROSS_LOGIN_JWT_HS512_KEY' env): JWT session login disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes, err := base64.StdEncoding.DecodeString(pubKey)
|
bytes, err := base64.StdEncoding.DecodeString(pubKey)
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -33,6 +32,10 @@ type LdapConfig struct {
|
|||||||
// Should a non-existent user be added to the DB if user exists in ldap directory
|
// Should a non-existent user be added to the DB if user exists in ldap directory
|
||||||
SyncUserOnLogin bool `json:"sync-user-on-login"`
|
SyncUserOnLogin bool `json:"sync-user-on-login"`
|
||||||
UpdateUserOnLogin bool `json:"update-user-on-login"`
|
UpdateUserOnLogin bool `json:"update-user-on-login"`
|
||||||
|
|
||||||
|
// Password for the LDAP admin account used for syncing (optional).
|
||||||
|
// Overridden by the LDAP_ADMIN_PASSWORD environment variable when set.
|
||||||
|
SyncPassword string `json:"sync-password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type LdapAuthenticator struct {
|
type LdapAuthenticator struct {
|
||||||
@@ -44,9 +47,9 @@ type LdapAuthenticator struct {
|
|||||||
var _ Authenticator = (*LdapAuthenticator)(nil)
|
var _ Authenticator = (*LdapAuthenticator)(nil)
|
||||||
|
|
||||||
func (la *LdapAuthenticator) Init() error {
|
func (la *LdapAuthenticator) Init() error {
|
||||||
la.syncPassword = os.Getenv("LDAP_ADMIN_PASSWORD")
|
la.syncPassword = secretFromEnv("LDAP_ADMIN_PASSWORD", Keys.LdapConfig.SyncPassword)
|
||||||
if la.syncPassword == "" {
|
if la.syncPassword == "" {
|
||||||
cclog.Warn("environment variable 'LDAP_ADMIN_PASSWORD' not set (ldap sync will not work)")
|
cclog.Warn("LDAP admin password not configured ('sync-password' in config or 'LDAP_ADMIN_PASSWORD' env): ldap sync will not work")
|
||||||
}
|
}
|
||||||
|
|
||||||
if Keys.LdapConfig.UserAttr != "" {
|
if Keys.LdapConfig.UserAttr != "" {
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ClusterCockpit/cc-backend/internal/repository"
|
"github.com/ClusterCockpit/cc-backend/internal/repository"
|
||||||
@@ -27,6 +26,14 @@ type OpenIDConfig struct {
|
|||||||
Provider string `json:"provider"`
|
Provider string `json:"provider"`
|
||||||
SyncUserOnLogin bool `json:"sync-user-on-login"`
|
SyncUserOnLogin bool `json:"sync-user-on-login"`
|
||||||
UpdateUserOnLogin bool `json:"update-user-on-login"`
|
UpdateUserOnLogin bool `json:"update-user-on-login"`
|
||||||
|
|
||||||
|
// OAuth2 client ID for the OIDC provider.
|
||||||
|
// Overridden by the OID_CLIENT_ID environment variable when set.
|
||||||
|
ClientID string `json:"client-id"`
|
||||||
|
|
||||||
|
// OAuth2 client secret for the OIDC provider.
|
||||||
|
// Overridden by the OID_CLIENT_SECRET environment variable when set.
|
||||||
|
ClientSecret string `json:"client-secret"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OIDC struct {
|
type OIDC struct {
|
||||||
@@ -66,13 +73,13 @@ func NewOIDC(a *Authentication) *OIDC {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
cclog.Fatal(err)
|
cclog.Fatal(err)
|
||||||
}
|
}
|
||||||
clientID := os.Getenv("OID_CLIENT_ID")
|
clientID := secretFromEnv("OID_CLIENT_ID", Keys.OpenIDConfig.ClientID)
|
||||||
if clientID == "" {
|
if clientID == "" {
|
||||||
cclog.Warn("environment variable 'OID_CLIENT_ID' not set (Open ID connect auth will not work)")
|
cclog.Warn("OIDC client ID not configured ('client-id' in config or 'OID_CLIENT_ID' env): Open ID connect auth will not work")
|
||||||
}
|
}
|
||||||
clientSecret := os.Getenv("OID_CLIENT_SECRET")
|
clientSecret := secretFromEnv("OID_CLIENT_SECRET", Keys.OpenIDConfig.ClientSecret)
|
||||||
if clientSecret == "" {
|
if clientSecret == "" {
|
||||||
cclog.Warn("environment variable 'OID_CLIENT_SECRET' not set (Open ID connect auth will not work)")
|
cclog.Warn("OIDC client secret not configured ('client-secret' in config or 'OID_CLIENT_SECRET' env): Open ID connect auth will not work")
|
||||||
}
|
}
|
||||||
|
|
||||||
client := &oauth2.Config{
|
client := &oauth2.Config{
|
||||||
|
|||||||
@@ -34,6 +34,22 @@ var configSchema = `
|
|||||||
"update-user-on-login": {
|
"update-user-on-login": {
|
||||||
"description": "Should an existent user attributes in the DB be updated at login attempt with values provided in JWT.",
|
"description": "Should an existent user attributes in the DB be updated at login attempt with values provided in JWT.",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"public-key": {
|
||||||
|
"description": "Base64 encoded Ed25519 public key used to validate JWTs. Overridden by the JWT_PUBLIC_KEY environment variable when set.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"private-key": {
|
||||||
|
"description": "Base64 encoded Ed25519 private key used to sign JWTs. Overridden by the JWT_PRIVATE_KEY environment variable when set.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"cross-login-public-key": {
|
||||||
|
"description": "Base64 encoded Ed25519 public key for accepting externally generated JWTs. Overridden by the CROSS_LOGIN_JWT_PUBLIC_KEY environment variable when set.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"cross-login-hs512-key": {
|
||||||
|
"description": "Base64 encoded HMAC (HS256/HS512) key for accepting externally generated session login tokens. Overridden by the CROSS_LOGIN_JWT_HS512_KEY environment variable when set.",
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["max-age"]
|
"required": ["max-age"]
|
||||||
@@ -52,6 +68,14 @@ var configSchema = `
|
|||||||
"update-user-on-login": {
|
"update-user-on-login": {
|
||||||
"description": "Should an existent user attributes in the DB be updated at login attempt with values provided.",
|
"description": "Should an existent user attributes in the DB be updated at login attempt with values provided.",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"client-id": {
|
||||||
|
"description": "OAuth2 client ID for the OIDC provider. Overridden by the OID_CLIENT_ID environment variable when set.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"client-secret": {
|
||||||
|
"description": "OAuth2 client secret for the OIDC provider. Overridden by the OID_CLIENT_SECRET environment variable when set.",
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["provider"]
|
"required": ["provider"]
|
||||||
@@ -103,6 +127,10 @@ var configSchema = `
|
|||||||
"update-user-on-login": {
|
"update-user-on-login": {
|
||||||
"description": "Should an existent user attributes in the DB be updated at login attempt with values from LDAP.",
|
"description": "Should an existent user attributes in the DB be updated at login attempt with values from LDAP.",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"sync-password": {
|
||||||
|
"description": "Password for the LDAP admin account used for syncing. Overridden by the LDAP_ADMIN_PASSWORD environment variable when set.",
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["url", "user-base", "search-dn", "user-bind", "user-filter"]
|
"required": ["url", "user-base", "search-dn", "user-bind", "user-filter"]
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ type ProgramConfig struct {
|
|||||||
|
|
||||||
APISubjects *NATSConfig `json:"api-subjects"`
|
APISubjects *NATSConfig `json:"api-subjects"`
|
||||||
|
|
||||||
// Drop root permissions once .env was read and the port was taken.
|
// Drop root permissions once the config was read and the port was taken.
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
Group string `json:"group"`
|
Group string `json:"group"`
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ var configSchema = `
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
"description": "Drop root permissions once .env was read and the port was taken. Only applicable if using privileged port.",
|
"description": "Drop root permissions once the config was read and the port was taken. Only applicable if using privileged port.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"description": "Drop root permissions once .env was read and the port was taken. Only applicable if using privileged port.",
|
"description": "Drop root permissions once the config was read and the port was taken. Only applicable if using privileged port.",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"disable-authentication": {
|
"disable-authentication": {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ MCowBQYDK2VwAyEA+51iXX8BdLFocrppRxIw52xCOf8xFSH/eNilN5IHVGc=
|
|||||||
Unfortunately, ClusterCockpit does not handle this format (yet). You can use this tool to convert the public PEM key into a representation for CC:
|
Unfortunately, ClusterCockpit does not handle this format (yet). You can use this tool to convert the public PEM key into a representation for CC:
|
||||||
|
|
||||||
```
|
```
|
||||||
CROSS_LOGIN_JWT_PUBLIC_KEY="+51iXX8BdLFocrppRxIw52xCOf8xFSH/eNilN5IHVGc="
|
cross-login-public-key: "+51iXX8BdLFocrppRxIw52xCOf8xFSH/eNilN5IHVGc="
|
||||||
```
|
```
|
||||||
|
|
||||||
Instructions
|
Instructions
|
||||||
@@ -19,7 +19,9 @@ Instructions
|
|||||||
- `cd tools/convert-pem-pubkey/`
|
- `cd tools/convert-pem-pubkey/`
|
||||||
- Insert your public ed25519 PEM key into `dummy.pub`
|
- Insert your public ed25519 PEM key into `dummy.pub`
|
||||||
- `go run . dummy.pub`
|
- `go run . dummy.pub`
|
||||||
- Copy the result into ClusterCockpit's `.env`
|
- Set the result as `cross-login-public-key` under `auth.jwts` in ClusterCockpit's
|
||||||
|
`config.json` (or supply it via the `CROSS_LOGIN_JWT_PUBLIC_KEY` environment
|
||||||
|
variable, which takes precedence)
|
||||||
- (Re)start ClusterCockpit backend
|
- (Re)start ClusterCockpit backend
|
||||||
|
|
||||||
Now CC can validate generated JWTs from the external provider.
|
Now CC can validate generated JWTs from the external provider.
|
||||||
|
|||||||
@@ -44,8 +44,11 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Print the value for use as auth.jwts.cross-login-public-key in config.json.
|
||||||
|
// It may alternatively be supplied via the CROSS_LOGIN_JWT_PUBLIC_KEY
|
||||||
|
// environment variable, which takes precedence.
|
||||||
fmt.Fprintf(os.Stdout,
|
fmt.Fprintf(os.Stdout,
|
||||||
"CROSS_LOGIN_JWT_PUBLIC_KEY=%#v\n",
|
"cross-login-public-key: %#v\n",
|
||||||
base64.StdEncoding.EncodeToString(pubkey))
|
base64.StdEncoding.EncodeToString(pubkey))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user