diff --git a/dev/certs/localhost/ca.crt b/dev/certs/localhost/ca.crt index 0f9cb685c4..996b406c72 100644 --- a/dev/certs/localhost/ca.crt +++ b/dev/certs/localhost/ca.crt @@ -1,11 +1,11 @@ -----BEGIN CERTIFICATE----- -MIIBjjCCATOgAwIBAgIUSGDgn+yJYhr4WwJE8/LuHIv8bRMwCgYIKoZIzj0EAwIw -FDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI1MDYwMjIwMTcwNVoXDTM1MDUzMTIw -MTcwNVowFDESMBAGA1UEAwwJbG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0D +MIIBjTCCATOgAwIBAgIUUbTC/rqeWN6ImaPKvseBPyts3bowCgYIKoZIzj0EAwIw +FDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDMwNTAxMDM0NVoXDTM2MDMwMjAx +MDM0NVowFDESMBAGA1UEAwwJbG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0D AQcDQgAEZOSlatLzFeE6/9bAaRs2ZGp0tWdVJsApXZSFI+ssNpWVHfl/xbsSg3s8 e/EPGyoaTl52B/7B6cphYZG/XMqfFqNjMGEwHQYDVR0OBBYEFP3XbiEfU2RSQlH9 UtoAy5yNsMqdMB8GA1UdIwQYMBaAFP3XbiEfU2RSQlH9UtoAy5yNsMqdMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA0kAMEYCIQCY -6aZXqaaXc4b0j1TywzNsC9S8fvtGhV9qxASzypmYrQIhAMMcVzTNqWh/hHeAWkgE -WgMORLUbzlhVYghrE7xdIkZH +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA0gAMEUCICKJ +XR3+kEW/e4jhyGezvLuSFlotUMAPqNgcNTuS3OWZAiEA3pcRA4YgZzlFGXCW/kWj +YeykmVGg+RezSlkJVgMPCC4= -----END CERTIFICATE----- diff --git a/dev/certs/localhost/client.crt b/dev/certs/localhost/client.crt index 2c6987f430..c72dadee1f 100644 --- a/dev/certs/localhost/client.crt +++ b/dev/certs/localhost/client.crt @@ -1,12 +1,13 @@ -----BEGIN CERTIFICATE----- -MIIBvTCCAWOgAwIBAgIUIL+x94GdtA9AvTeCyIjwNA7rnbYwCgYIKoZIzj0EAwIw -FDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI1MDYwMjIwMTcwNVoXDTI2MDYwMjIw -MTcwNVowFjEUMBIGA1UEAwwLVGVzdCBDbGllbnQwWTATBgcqhkjOPQIBBggqhkjO +MIIB4DCCAYWgAwIBAgIUZ7biiH4XMAS9+fPHFMAWX9odf+8wCgYIKoZIzj0EAwIw +FDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDMwNTAxMDM0NVoXDTI3MDMwNTAx +MDM0NVowFjEUMBIGA1UEAwwLVGVzdCBDbGllbnQwWTATBgcqhkjOPQIBBggqhkjO PQMBBwNCAATH1LS45MUYeiluQFEeJa2vcgwaiYGhpVLsDuEPdet8bk8K9Ic7PWRv -uv32g6a+gq3Zchzu1PHoeUiXRaeGsji/o4GQMIGNMAkGA1UdEwQCMAAwCwYDVR0P -BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAUBgNVHREEDTAL -gglsb2NhbGhvc3QwHQYDVR0OBBYEFO3x9xTYUVtn7x9hzCNLUODQLmLNMB8GA1Ud -IwQYMBaAFP3XbiEfU2RSQlH9UtoAy5yNsMqdMAoGCCqGSM49BAMCA0gAMEUCIH30 -DVy+DPR0V/v9T1XJ8HhlUg8UbPmcwTgzavL42syFAiEA/KKtNnVBEZ6+m7fDIulS -5sD2UZxwVkLBGJTFLRwwxmU= +uv32g6a+gq3Zchzu1PHoeUiXRaeGsji/o4GyMIGvMAkGA1UdEwQCMAAwCwYDVR0P +BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjA2BgNVHREELzAt +gglsb2NhbGhvc3SCFGhvc3QuZG9ja2VyLmludGVybmFshwR/AAABhwTAqEH+MB0G +A1UdDgQWBBTt8fcU2FFbZ+8fYcwjS1Dg0C5izTAfBgNVHSMEGDAWgBT9124hH1Nk +UkJR/VLaAMucjbDKnTAKBggqhkjOPQQDAgNJADBGAiEAoL14N3nkpHUmnhF7ErRx +QszmESCUs5WTHFMSy/FfdtUCIQCNKigh/n+TwsYZfyKu2XP9Fn16Crt6JxhUtJZf +OwFloQ== -----END CERTIFICATE----- diff --git a/dev/certs/localhost/gen-certs.sh b/dev/certs/localhost/gen-certs.sh index db5df39e82..50b58678b3 100755 --- a/dev/certs/localhost/gen-certs.sh +++ b/dev/certs/localhost/gen-certs.sh @@ -46,9 +46,12 @@ keyUsage = digitalSignature, keyEncipherment extendedKeyUsage = serverAuth, clientAuth subjectAltName = @alt_names +# host.docker.internal and 192.168.65.254 are docker 'magic' name/ip to communicate with host on macOS. [ alt_names ] DNS.1 = localhost +DNS.2 = host.docker.internal IP.1 = 127.0.0.1 +IP.2 = 192.168.65.254 EOF # Generate CA key and self-signed certificate diff --git a/dev/certs/localhost/localhost.crt b/dev/certs/localhost/localhost.crt index 7b325b4aed..4123d44a21 100644 --- a/dev/certs/localhost/localhost.crt +++ b/dev/certs/localhost/localhost.crt @@ -1,12 +1,13 @@ -----BEGIN CERTIFICATE----- -MIIBwjCCAWegAwIBAgIUU8w1DlbAnCBW8vM15Ni1vCzk2GQwCgYIKoZIzj0EAwIw -FDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI1MDYxMDE2NDcxNloXDTI2MDYxMDE2 -NDcxNlowFDESMBAGA1UEAwwJbG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0D +MIIB3TCCAYOgAwIBAgIUZ7biiH4XMAS9+fPHFMAWX9odf+4wCgYIKoZIzj0EAwIw +FDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDMwNTAxMDM0NVoXDTI3MDMwNTAx +MDM0NVowFDESMBAGA1UEAwwJbG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0D AQcDQgAEUcNhl5in7twuxJSVS0PJ9lx/nyChb0gt2t4EqBh0EXirXwOv0UgAlTtU -ySW+gz6G/fhnvU1VTEZj1R9ROmAUjqOBljCBkzAJBgNVHRMEAjAAMAsGA1UdDwQE -AwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwGgYDVR0RBBMwEYIJ -bG9jYWxob3N0hwR/AAABMB0GA1UdDgQWBBQGkf9KiSeLoePk7VNsnNpwoLcj+DAf -BgNVHSMEGDAWgBT9124hH1NkUkJR/VLaAMucjbDKnTAKBggqhkjOPQQDAgNJADBG -AiEAjApLmLwEvi+oJNmpPZdDuYxg5LDZMyyiHpoTPCQWGiYCIQChuNsE2DfeEOMI -BM9NCIxPiaiKbzP+Abh3r58TRH0XPg== +ySW+gz6G/fhnvU1VTEZj1R9ROmAUjqOBsjCBrzAJBgNVHRMEAjAAMAsGA1UdDwQE +AwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwNgYDVR0RBC8wLYIJ +bG9jYWxob3N0ghRob3N0LmRvY2tlci5pbnRlcm5hbIcEfwAAAYcEwKhB/jAdBgNV +HQ4EFgQUBpH/Sokni6Hj5O1TbJzacKC3I/gwHwYDVR0jBBgwFoAU/dduIR9TZFJC +Uf1S2gDLnI2wyp0wCgYIKoZIzj0EAwIDSAAwRQIgUtrkxbKFr/SxzcE4FbI39ep6 +jQPy7pUoPZGWue1uYkkCIQDeunYP19qD1+k/sEdt/8xwbFL4BA2211+s017BoRwv +IA== -----END CERTIFICATE----- diff --git a/dev/mac-local-dev/README.md b/dev/mac-local-dev/README.md index eb96e6a085..a4982db13b 100644 --- a/dev/mac-local-dev/README.md +++ b/dev/mac-local-dev/README.md @@ -1,246 +1,179 @@ -# Local Carbide API (without machine-a-tron for now) +# Mac Local Development — Carbide API -Notes: -- technically you can start machine-a-tron but it is useless on a Mac since its magic relies on Linux-specific features. It may work in docker on Mac... -- which is why we should run carbide on Mac in docker too. But for now this run native on Mac. +Runs `carbide-api` natively on macOS (no Docker for the binary itself). +Docker Desktop is used only for Vault and Postgres. +This Carbide API instance is usable by Carbide REST stack. -## To run carbide from the carbide directory: -This will setup everything and run carbide-api binary. +> **Limitations** +> - TPM / attestation features require Linux and a physical TPM — they are disabled in this setup. +> - `machine-a-tron` relies on Linux-specific features and is not useful on macOS. -You can verify carbide-api is running by doing: -```bash -grpcurl -plaintext localhost:1079 list -```` -If you configure carbide to run with TLS , you can do: -```bash -grpcurl -insecure localhost:1079 list -``` +## Prerequisites -## To run carbide in IntelliJ/RustRover IDE +| Tool | Notes | +|------|-------| +| Docker Desktop | Must be running before the script is invoked | +| Rust toolchain | `cargo` must be on `$PATH` | +| `jq` | JSON processing for Vault init output | +| `curl` | Vault health-check polling | +| `openssl` | TLS cert generation (pre-installed on macOS) | -IDE setup is not complete: you may want to configure 'Rust -> External Linters -> Additional Arguments' to include '--no-default-features'. +--- -First run carbide stand-alone the kill it (it does setup everything).
-Then get some light setup and the needed variables by running: -```bash -SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt dev/mac-local-dev/set-env.sh -``` -It should output something like: -```bash -# required variables to run carbide-api: -export VAULT_PKI_ROLE_NAME=role -export VAULT_ADDR=http://localhost:8200 -export CARBIDE_WEB_OAUTH2_CLIENT_SECRET= -export VAULT_PKI_MOUNT_LOCATION=certs -export VAULT_KV_MOUNT_LOCATION=secrets -export VAULT_TOKEN= -export CARBIDE_WEB_AUTH_TYPE=oauth2 -export CARBIDE_WEB_PRIVATE_COOKIEJAR_KEY= -export DATABASE_URL=postgresql://postgres:admin@localhost - -You will need to create/modify a run configuration for carbide-api. -- cargo command parameters: -``` -run --package carbide-api ---no-default-features -- run ---config-path dev/mac-local-dev/carbide-api-config-custom.toml -``` -- environment variables: copy/paste the single line output from above into the 'Environment variables' section. +## Starting Carbide API -# Setting a local carbide-api for cloud-local +Run from **any directory** — the script resolves the repo root automatically: -BEFORE setting the cloud-local environment you MUST configure auth by replacing 'kas-legacy' in `deploy/kustomize/overlays/local/configmap.yaml` by the following: -```yaml -- name: nvidia - origin: 4 # TokenOriginCustom - url: https://stg.authn.nvidia.com/pubJWKS - issuer: "stg.auth.ngc.nvidia.com" - serviceAccount: True +```bash +./dev/mac-local-dev/run-carbide-api.sh ``` -Setup cloud-local (<8m): +The script is fully self-contained and idempotent. On each run it: + +1. Checks prerequisites (`docker`, `cargo`, `jq`, `curl`). +2. Starts a **Vault** container (`carbide-vault`) on port **8201** and initialises it + (KV secrets + PKI) if not already running. The root token is cached at + `/tmp/carbide-localdev-vault-root-token`. +3. Regenerates **TLS certificates** under `dev/certs/localhost/` if they are + missing or stale (`gen-certs.sh` is idempotent). +4. Starts a **Postgres** container (`pgdev`) on port **5432** with SSL if not + already running. +5. Creates `/opt/carbide/firmware` (may prompt for `sudo` once). +6. Writes a temporary resolved config to `/tmp/carbide-api-config-.toml` + with absolute TLS cert paths (the checked-in config uses paths relative to + `$CWD`, which would break when launched from an IDE). +7. Runs **database migrations**. +8. Starts `carbide-api` (foreground, `Ctrl-C` to stop). + +Once running: + ```bash -cd cloud-local -scripts/setup-forge-cloud.sh --clean +# Verify gRPC is up +grpcurl -insecure localhost:1079 list + +# Web UI +open https://localhost:1079/admin ``` -Make available the local carbide-api to cloud-local by running: +### Resetting state + ```bash -cd cloud-local -scripts/setup-local-carbide-service.sh +# Remove containers (preserves cert files) +docker rm -f carbide-vault pgdev + +# Also regenerate certs from scratch +rm -f dev/certs/localhost/*.crt dev/certs/localhost/*.key ``` -Start the elektra-site-agent: -```bash -cd cloud-local -scripts/setup-site.sh -```` +--- +## Using carbide-admin-cli -Make available the local carbide-api to cloud-local by running: -- use SSH port forwarding to expose carbide-api running remotely on your local machine.
- Example: `ssh -L 10443:10.217.117.194:443 mydev` - You can check access by going to -- configure cloud-local to use your remote carbide-api by running: -```bash -cd cloud-local -scripts/setup-dev-carbide-service.sh -``` +In a **second terminal**, use the wrapper script to talk to the running API: -Start the elektra-site-agent: ```bash -cd cloud-local -scripts/setup-site.sh -```` +./dev/mac-local-dev/run-carbide-admin-cli.sh [args...] +``` -# End-to-end test +The script: +- Builds `carbide-admin-cli` automatically if `target/debug/carbide-admin-cli` + does not exist. +- Wires up TLS using the locally-generated certs from `dev/certs/localhost/` + (the same CA that `run-carbide-api.sh` configures the server to trust). +- Can be run from any directory. -## create a tenant and retrieve the Tenant ID +### Global flags -Create and retrieve Tenant by doing a 'Get Current Tenant' request... +| Flag | Short | Description | +|------|-------|-------------| +| `--format ` | `-f` | `ascii-table` (default), `json`, … | +| `--carbide-api ` | `-c` | Override API URL | +| `--output ` | `-o` | Write output to file | +| `--extended` | | Include internal UUIDs and extra fields | +| `--sort-by ` | | `primary-id` (default) or `state` | +| `--debug` | `-d` | Increase log verbosity (repeat for trace) | +| `--internal-page-size N` | `-p` | Paging size for list calls (default 100) | -Take note of the Tenant ID for subsequent requests. +### Common subcommands -## retrieve the Site ID +```bash +# List all machines +./dev/mac-local-dev/run-carbide-admin-cli.sh machine list -WARNING: you may have to wait for site to be in 'Registered' state before proceeding. +# Show details for a specific machine +./dev/mac-local-dev/run-carbide-admin-cli.sh machine show -Retrieve the Site ID by doing a 'Get All Sites' request... +# List OS images +./dev/mac-local-dev/run-carbide-admin-cli.sh os-image list -Take note of the Site ID for subsequent requests. +# List network segments +./dev/mac-local-dev/run-carbide-admin-cli.sh network-segment list -## create an ip block +# List tenants (JSON output) +./dev/mac-local-dev/run-carbide-admin-cli.sh --format json tenant show -```json -{ - "name": "allocation-test-super-block", - "description": "IP Super block for Allocation test", - "siteId": "{{siteId}}", - "routingType": "DatacenterOnly", - "prefix": "100.100.0.0", - "prefixLength": 19, - "protocolVersion": "IPv4" -} -``` -Take note of the IP Block ID for subsequent requests. - -## create an allocation - -Use the ID of the above created IP Block as resourceTypeId. - -```json -{ - "name": "allocation-test-dont-use-ip-block", - "description": "Allocation Test IP Block for Demo Tenant", - "tenantId": "{{tenantId}}", - "siteId": "{{siteId}}", - "allocationConstraints": [ - { - "resourceType": "IPBlock", - "resourceTypeId": "3b697bc7-27e2-479c-8152-51b33bfd4c5a", - "constraintType": "Reserved", - "constraintValue": 19 - } - ] -} +# Explore all available subcommands +./dev/mac-local-dev/run-carbide-admin-cli.sh --help +# Explore sub-subcommands +./dev/mac-local-dev/run-carbide-admin-cli.sh machine --help ``` -## create a VPC (this will reach out to carbide API) -Ensure Site is in 'Registered' state before creating VPC: it should happen automatically if there is a proper connection already. - -```json -{ - "name": "capi-test-vpc", - "description": "VPC for Testing CAPI Integration", - "siteId": "{{siteId}}" -} -``` - -# PREVIOUS INFORMATION FOR REFERENCE
YOU SHOULD PROBABLY IGNORE FOR NOW -Running machine-a-tron against carbide API locally. +### Environment variable overrides -Requires `sops` and corresponding key in `~/Library/Application Support/sops/age/keys.txt` -(just like with the normal development environment). +| Variable | Default | Purpose | +|----------|---------|---------| +| `CARBIDE_API_URL` | `https://localhost:1079` | API endpoint | +| `FORGE_ROOT_CA_PATH` | `dev/certs/localhost/ca.crt` | CA used to verify the server cert | +| `CLIENT_CERT_PATH` | `dev/certs/localhost/client.crt` | mTLS client certificate | +| `CLIENT_KEY_PATH` | `dev/certs/localhost/client.key` | mTLS client key | +### Expired certificate errors -## Setup Vault and Postgres +If you see `invalid peer certificate: Expired`, the certs in +`dev/certs/localhost/` need to be regenerated: - -Run vault and add the site-wide secrets that carbide API depends on. ```bash -# I used vault version Vault v1.20.2 (824d12909d5b596ddd3f34d9c8f169b4f9701a0c), built 2025-08-05T19:05:39Z -docker run --rm --detach --name vault --cap-add=IPC_LOCK -e 'VAULT_LOCAL_CONFIG={"storage": {"file": {"path": "/vault/file"}}, "listener": [{"tcp": { "address": "0.0.0.0:8200", "tls_disable": true}}], "default_lease_ttl": "168h", "max_lease_ttl": "720h", "ui": true}' -p 8200:8200 hashicorp/vault server -docker exec -it vault sh - -export VAULT_ADDR="http://127.0.0.1:8200" -vault operator init -key-shares=1 -key-threshold=1 -format=json -# copy out unseal_keys_b64 and root_token -# don't forget any trailing '='s -# save the ROOT_TOKEN for running carbide API later -export UNSEAL_KEY="base64 encoded data" -export ROOT_TOKEN="hvs.something" -vault operator unseal $UNSEAL_KEY -vault login $ROOT_TOKEN -vault secrets enable -path=secrets -version=2 kv -vault kv delete /secrets/machines/bmc/site/root -vault kv delete /secrets/machines/all_dpus/site_default/uefi-metadata-items/auth -vault kv delete /secrets/machines/all_hosts/site_default/uefi-metadata-items/auth -echo '{"UsernamePassword": {"username": "root", "password": "vault-password" }}' | vault kv put /secrets/machines/bmc/site/root - -echo '{"UsernamePassword": {"username": "root", "password": "vault-password" }}' | vault kv put /secrets/machines/all_dpus/site_default/uefi-metadata-items/auth - -echo '{"UsernamePassword": {"username": "root", "password": "vault-password" }}' | vault kv put /secrets/machines/all_hosts/site_default/uefi-metadata-items/auth - -vault secrets enable -path=certs pki -vault write certs/root/generate/internal common_name=myvault.com ttl=87600h -vault write certs/config/urls issuing_certificates="http://vault.example.com:8200/v1/pki/ca" crl_distribution_points="http://vault.example.com:8200/v1/pki/crl" -vault write certs/roles/role allowed_domains=example.com allow_subdomains=true max_ttl=72h require_cn=false allowed_uri_sans="spiffe://forge.local/*" +rm -f dev/certs/localhost/*.crt dev/certs/localhost/*.key +(cd dev/certs/localhost && ./gen-certs.sh) ``` -Run postgres with certs. -```bash -cd dev/certs/localhost -./gen-certs.sh -bash -c 'docker run --rm --detach --name pgdev --net=host -e POSTGRES_PASSWORD="admin" -e POSTGRES_HOST_AUTH_METHOD=trust -v "$(pwd)/localhost.crt:/var/lib/postgresql/server.crt:ro" -v "$(pwd)/localhost.key:/var/lib/postgresql/server.key:ro" postgres:14.5-alpine -c ssl=on -c ssl_cert_file=/var/lib/postgresql/server.crt -c ssl_key_file=/var/lib/postgresql/server.key -c max_connections=300' -``` +Then restart `run-carbide-api.sh` (the API must load the new server cert). -## Run Carbide API +> **Note:** `dev/certs/server_identity.pem` and +> `dev/certs/forge_developer_local_only_root_cert_pem` are checked-in certs +> that expired in 2023/2024. Do **not** use them — the scripts default to the +> locally-generated `localhost/` certs instead. -```bash -FORGED_PATH="../forged" -export CARBIDE_WEB_OAUTH2_CLIENT_SECRET=$(sops -d $FORGED_PATH/bases/carbide/api/secrets/azure-carbide-web-sso-NONPRODUCTION.enc.yaml | sed -En 's/.*client_secret: (.*)/\1/p' | base64 -d) -export CARBIDE_WEB_AUTH_TYPE=oauth2 -export CARBIDE_WEB_PRIVATE_COOKIEJAR_KEY=$(openssl rand -base64 64) -export DATABASE_URL="postgresql://postgres:admin@localhost" +--- -export VAULT_ADDR="http://localhost:8200" -export VAULT_KV_MOUNT_LOCATION="secrets" -export VAULT_PKI_MOUNT_LOCATION="certs" -export VAULT_PKI_ROLE_NAME="role" -# copy the vault root token -export VAULT_TOKEN="hvs.something" +## Running carbide-api from an IDE (RustRover / IntelliJ) -# Run SQL migrations. -cargo run --package carbide-api --no-default-features migrate +IDE setup is not complete; you may want to set +**Rust → External Linters → Additional Arguments** to `--no-default-features`. -sudo mkdir /opt/carbide/firmware # carbide expects this directory to exist (even if empty). +Run `./dev/mac-local-dev/run-carbide-api.sh` once to completion, then kill it — +this ensures Vault and Postgres are initialised and the token file exists. -RUST_BACKTRACE=1 cargo run --package carbide-api --no-default-features -- run --config-path dev/mac-local-dev/carbide-api-config.toml -``` +Retrieve the environment variables for the run configuration: -In another terminal, run machine-a-tron. ```bash -sudo echo sudo enabled # get sudo without password (so we don't have to run cargo as root) - -REPO_ROOT=. cargo run --bin machine-a-tron dev/machine-a-tron/config/mac.toml --forge-root-ca-path /Users/fchua/repos/carbide/dev/certs/localhost/ca.crt --client-cert-path /Users/fchua/repos/carbide/dev/certs/localhost/localhost.crt --client-key-path /Users/fchua/repos/carbide/dev/certs/localhost/localhost.key +echo "CARBIDE_WEB_AUTH_TYPE=basic" +echo "DATABASE_URL=postgresql://postgres:admin@localhost" +echo "VAULT_ADDR=http://localhost:8201" +echo "VAULT_KV_MOUNT_LOCATION=secrets" +echo "VAULT_PKI_MOUNT_LOCATION=certs" +echo "VAULT_PKI_ROLE_NAME=role" +echo "VAULT_TOKEN=$(cat /tmp/carbide-localdev-vault-root-token)" ``` +Cargo run parameters: +``` +run --package carbide-api --no-default-features -- run +--config-path /dev/mac-local-dev/carbide-api-config.toml +``` -[//]: # (Edit your config map entry 'carbide_address' to point to :1079 then restart the elektra-site-agent pods.) - -[//]: # (```bash) - -[//]: # (kubectl edit configmap elektra-config-map-6745d4gct5 -n elektra-site-agent) - -[//]: # (kubectl delete pod elektra-site-agent-0 elektra-site-agent-1 elektra-site-agent-2 -n elektra-site-agent) - -[//]: # (```) +> The config file uses CWD-relative TLS paths. Set the IDE run configuration's +> **Working Directory** to the repository root, or use the absolute-path temp +> config that `run-carbide-api.sh` writes to `/tmp/carbide-api-config-.toml`. diff --git a/dev/mac-local-dev/carbide-api-config.toml b/dev/mac-local-dev/carbide-api-config.toml index 413f5fe50b..ca7dde32c8 100644 --- a/dev/mac-local-dev/carbide-api-config.toml +++ b/dev/mac-local-dev/carbide-api-config.toml @@ -15,7 +15,7 @@ # limitations under the License. # listen = "[::]:1079" -listen_mode = "plaintext_http2" +listen_mode = "tls" metrics_endpoint = "[::]:1080" database_url = "postgresql://postgres:admin@localhost" asn = 4294967000 @@ -57,11 +57,22 @@ hardware_health_reports = "MonitorOnly" [firmware_global] autoupdate = true +# NOTE: carbide-api resolves these paths relative to the process working +# directory, NOT relative to this config file. Do NOT run carbide-api +# directly with this file from an IDE or any directory other than the repo +# root — cert loading will fail silently. +# run-carbide-api.sh works around this by copying the config to /tmp and +# rewriting these values to absolute paths before passing it to the binary. [tls] -identity_pemfile_path = "/Users/fchua/repos/carbide/dev/certs/localhost/localhost.crt" -identity_keyfile_path = "/Users/fchua/repos/carbide/dev/certs/localhost/localhost.key" -root_cafile_path = "/Users/fchua/repos/carbide/dev/certs/localhost/ca.crt" -admin_root_cafile_path = "/Users/fchua/repos/carbide/dev/certs/forge_root.pem" +identity_pemfile_path = "dev/certs/localhost/localhost.crt" +identity_keyfile_path = "dev/certs/localhost/localhost.key" +root_cafile_path = "dev/certs/localhost/ca.crt" +# Use the same local CA for admin client certs so that the client cert +# generated by dev/certs/localhost/gen-certs.sh is accepted. The +# checked-in dev/certs/forge_root.pem is the real NVIDIA Forge Root CA +# whose private key is not available locally, making it impossible to +# issue valid admin client certs against it for local dev. +admin_root_cafile_path = "dev/certs/localhost/ca.crt" [auth] permissive_mode = true diff --git a/dev/mac-local-dev/run-carbide-admin-cli.sh b/dev/mac-local-dev/run-carbide-admin-cli.sh new file mode 100755 index 0000000000..0ec0541f65 --- /dev/null +++ b/dev/mac-local-dev/run-carbide-admin-cli.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Wrapper to run carbide-admin-cli against the local dev carbide-api instance +# started by run-carbide-api.sh. +# +# Usage (from repo root or any directory): +# ./dev/mac-local-dev/run-carbide-admin-cli.sh [args...] +# +# Examples: +# ./dev/mac-local-dev/run-carbide-admin-cli.sh version +# ./dev/mac-local-dev/run-carbide-admin-cli.sh machine show +# ./dev/mac-local-dev/run-carbide-admin-cli.sh ipxe-template list +# ./dev/mac-local-dev/run-carbide-admin-cli.sh ipxe-template get ubuntu-24.04-netboot +# ./dev/mac-local-dev/run-carbide-admin-cli.sh os-image show +# ./dev/mac-local-dev/run-carbide-admin-cli.sh --format json ipxe-template list +# + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" + +export REPO_ROOT="$REPO_ROOT" +# Default to the locally-generated certs produced by dev/certs/localhost/gen-certs.sh. +# server_identity.pem / forge_developer_local_only_root_cert_pem are checked-in +# certs that have long expired and cannot be renewed without the NVIDIA CA private key. +CARBIDE_API_URL="${CARBIDE_API_URL:-https://localhost:1079}" +FORGE_ROOT_CA_PATH="${FORGE_ROOT_CA_PATH:-$REPO_ROOT/dev/certs/localhost/ca.crt}" +CLIENT_CERT_PATH="${CLIENT_CERT_PATH:-$REPO_ROOT/dev/certs/localhost/client.crt}" +CLIENT_KEY_PATH="${CLIENT_KEY_PATH:-$REPO_ROOT/dev/certs/localhost/client.key}" + +CLI_BIN="$REPO_ROOT/target/debug/carbide-admin-cli" + +if [ ! -x "$CLI_BIN" ]; then + echo "Binary not found at $CLI_BIN — building first..." + cargo build -p carbide-admin-cli --manifest-path "$REPO_ROOT/Cargo.toml" +fi + +exec "$CLI_BIN" \ + --carbide-api "$CARBIDE_API_URL" \ + --forge-root-ca-path "$FORGE_ROOT_CA_PATH" \ + --client-cert-path "$CLIENT_CERT_PATH" \ + --client-key-path "$CLIENT_KEY_PATH" \ + "$@" diff --git a/dev/mac-local-dev/run-carbide-api.sh b/dev/mac-local-dev/run-carbide-api.sh new file mode 100755 index 0000000000..a903d28420 --- /dev/null +++ b/dev/mac-local-dev/run-carbide-api.sh @@ -0,0 +1,187 @@ +#!/usr/bin/env bash +# +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +# +# Self-contained script to start carbide-api on macOS. +# Run from the repository root: ./dev/mac-local-dev/run-carbide-api.sh +# +# Prerequisites: Docker Desktop, Rust toolchain, jq +# + +set -euo pipefail + +# ----------------------------------------------------------------------------- +# Configuration +# ----------------------------------------------------------------------------- +VAULT_CONTAINER="carbide-vault" +VAULT_PORT=8201 +VAULT_ADDR="http://localhost:$VAULT_PORT" +TOKEN_FILE="/tmp/carbide-localdev-vault-root-token" +PG_CONTAINER="pgdev" + +# ----------------------------------------------------------------------------- +# Helpers +# ----------------------------------------------------------------------------- +die() { + echo "❌ $*" >&2 + exit 1 +} + +info() { + echo "ℹ️ $*" +} + +ok() { + echo "✓ $*" +} + +# Ensure we're in the repo root +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$REPO_ROOT" + +# ----------------------------------------------------------------------------- +# Prerequisites +# ----------------------------------------------------------------------------- +echo "" +echo "=== Carbide API - Mac Local Development ===" +echo "" + +for cmd in docker cargo jq curl; do + command -v "$cmd" >/dev/null 2>&1 || die "$cmd not found. Please install it." +done +ok "Required binaries: docker, cargo, jq, curl" + +if ! docker ps >/dev/null 2>&1; then + die "Docker is not running. Start Docker Desktop." +fi +ok "Docker is running" + +# ----------------------------------------------------------------------------- +# Start Vault (port 8201 - dedicated for carbide, avoids kind cluster conflict) +# ----------------------------------------------------------------------------- +if docker ps --format '{{.Names}}' | grep -w "$VAULT_CONTAINER" >/dev/null; then + ok "Vault container already running" + [ -f "$TOKEN_FILE" ] || die "Token file missing. Remove container and retry: docker rm -f $VAULT_CONTAINER" + chmod 600 "$TOKEN_FILE" +else + info "Starting Vault on port $VAULT_PORT..." + docker rm -f "$VAULT_CONTAINER" 2>/dev/null || true + + docker run --rm --detach --name "$VAULT_CONTAINER" --cap-add=IPC_LOCK \ + -e 'VAULT_LOCAL_CONFIG={"storage": {"file": {"path": "/vault/file"}}, "listener": [{"tcp": { "address": "0.0.0.0:8200", "tls_disable": true}}], "default_lease_ttl": "168h", "max_lease_ttl": "720h", "ui": true}' \ + -p "$VAULT_PORT:8200" hashicorp/vault:1.20.2 server >/dev/null 2>&1 || die "Failed to start vault" + + echo "Waiting for vault..." + sleep 2 + until curl -s "$VAULT_ADDR/v1/sys/health" >/dev/null 2>&1; do sleep 1; done + + info "Initializing vault..." + INIT=$(docker exec "$VAULT_CONTAINER" sh -c "export VAULT_ADDR=http://127.0.0.1:8200; vault operator init -key-shares=1 -key-threshold=1 -format=json") + UNSEAL_KEY=$(echo "$INIT" | jq -r ".unseal_keys_b64[0]") + ROOT_TOKEN=$(echo "$INIT" | jq -r ".root_token") + (umask 077 && echo "$ROOT_TOKEN" > "$TOKEN_FILE") + + info "Configuring vault secrets..." + docker exec "$VAULT_CONTAINER" sh -c " + export VAULT_ADDR=http://127.0.0.1:8200 + vault operator unseal \"$UNSEAL_KEY\" + vault login \"$ROOT_TOKEN\" + vault secrets enable -path=secrets -version=2 kv + echo '{\"UsernamePassword\": {\"username\": \"root\", \"password\": \"vault-password\" }}' | vault kv put /secrets/machines/bmc/site/root - + echo '{\"UsernamePassword\": {\"username\": \"root\", \"password\": \"vault-password\" }}' | vault kv put /secrets/machines/all_dpus/site_default/uefi-metadata-items/auth - + echo '{\"UsernamePassword\": {\"username\": \"root\", \"password\": \"vault-password\" }}' | vault kv put /secrets/machines/all_hosts/site_default/uefi-metadata-items/auth - + vault secrets enable -path=certs pki + vault write certs/root/generate/internal common_name=myvault.com ttl=87600h + vault write certs/config/urls issuing_certificates=\"http://vault.example.com:8200/v1/pki/ca\" crl_distribution_points=\"http://vault.example.com:8200/v1/pki/crl\" + vault write certs/roles/role allowed_domains=example.com allow_subdomains=true max_ttl=72h require_cn=false allowed_uri_sans=\"spiffe://forge.local/*\" + " >/dev/null 2>&1 + + ok "Vault initialized at $VAULT_ADDR" +fi + +# ----------------------------------------------------------------------------- +# TLS certificates +# Runs unconditionally; gen-certs.sh is idempotent (skips files that exist +# and are newer than their signing key), so this is fast on subsequent runs. +# ----------------------------------------------------------------------------- +info "Ensuring TLS certificates are up to date..." +(cd "$REPO_ROOT/dev/certs/localhost" && ./gen-certs.sh) >/dev/null 2>&1 +ok "TLS certificates ready" + +# ----------------------------------------------------------------------------- +# Start Postgres +# ----------------------------------------------------------------------------- +if docker ps --format '{{.Names}}' | grep -w "$PG_CONTAINER" >/dev/null; then + ok "Postgres already running" +else + CERTS_DIR="$REPO_ROOT/dev/certs/localhost" + info "Starting Postgres..." + docker run --rm --detach --name "$PG_CONTAINER" \ + -e POSTGRES_PASSWORD="admin" \ + -e POSTGRES_HOST_AUTH_METHOD=trust \ + -v "$CERTS_DIR/localhost.crt:/var/lib/postgresql/server.crt:ro" \ + -v "$CERTS_DIR/localhost.key:/var/lib/postgresql/server.key:ro" \ + -p 5432:5432 \ + postgres:14.5-alpine \ + -c ssl=on \ + -c ssl_cert_file=/var/lib/postgresql/server.crt \ + -c ssl_key_file=/var/lib/postgresql/server.key \ + -c max_connections=300 >/dev/null 2>&1 || die "Failed to start postgres" + + sleep 2 + ok "Postgres started" +fi + +# ----------------------------------------------------------------------------- +# Environment +# ----------------------------------------------------------------------------- +export CARBIDE_WEB_AUTH_TYPE="${CARBIDE_WEB_AUTH_TYPE:-basic}" +export DATABASE_URL="postgresql://postgres:admin@localhost" +export VAULT_ADDR="$VAULT_ADDR" +export VAULT_KV_MOUNT_LOCATION="secrets" +export VAULT_PKI_MOUNT_LOCATION="certs" +export VAULT_PKI_ROLE_NAME="role" +export VAULT_TOKEN="$(cat "$TOKEN_FILE")" + +# ----------------------------------------------------------------------------- +# Firmware directory (carbide expects this) +# ----------------------------------------------------------------------------- +if [ ! -d /opt/carbide/firmware ]; then + info "Creating /opt/carbide/firmware (may prompt for password)..." + sudo mkdir -p /opt/carbide/firmware +fi + +# ----------------------------------------------------------------------------- +# Generate a resolved config with absolute TLS paths +# +# carbide-api opens TLS paths relative to the process working directory, not +# relative to the config file. The checked-in config uses relative paths +# (e.g. "dev/certs/…") which only work when CWD == repo root. When launched +# from an IDE or any other directory the cert load will silently fail. +# We rewrite those paths to absolute ones in a throwaway /tmp copy so the +# binary is always given correct paths regardless of CWD. +# ----------------------------------------------------------------------------- +CARBIDE_TMP_CONFIG="/tmp/carbide-api-config-$$.toml" +sed "s|= \"dev/|= \"$REPO_ROOT/dev/|g" \ + "$REPO_ROOT/dev/mac-local-dev/carbide-api-config.toml" > "$CARBIDE_TMP_CONFIG" +ok "Resolved config written to $CARBIDE_TMP_CONFIG" + +# ----------------------------------------------------------------------------- +# Migrations & Run +# ----------------------------------------------------------------------------- +echo "" +echo "=== Running migrations ===" +cargo run --package carbide-api --no-default-features migrate || die "Database migrations failed; fix the issue above and re-run this script." + +echo "" +echo "=== Starting Carbide API ===" +info "TPM/attestation features are not supported on Mac (requires Linux + TPM)." +echo " All other functionality is available." +echo "" +echo " Web UI: https://localhost:1079/admin" +echo " gRPC: grpcurl -insecure localhost:1079 list" +echo "" + +exec env RUST_BACKTRACE=1 cargo run --package carbide-api --no-default-features -- run \ + --config-path "$CARBIDE_TMP_CONFIG" diff --git a/dev/mac-local-dev/set-env.sh b/dev/mac-local-dev/set-env.sh deleted file mode 100755 index 80e6cc77c4..0000000000 --- a/dev/mac-local-dev/set-env.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash -# -# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 -# -# Licensed 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. -# -# set-env.sh -# -# Generate customized config and display environment variables needed to run a native carbide-api on MacOS. -# - -set -e - -CUR_DIR="$(pwd)" - -# customize config to point to local certificates: -CUSTOM_CONFIG="dev/mac-local-dev/carbide-api-config-custom.toml" -sed -e 's|/.*carbide/dev|'"$CUR_DIR"'/dev|' < dev/mac-local-dev/carbide-api-config.toml > "${CUSTOM_CONFIG}" - -export DATABASE_URL="postgresql://postgres:admin@localhost" - -export CARBIDE_WEB_AUTH_TYPE=oauth2 -export CARBIDE_WEB_OAUTH2_CLIENT_SECRET=${CARBIDE_WEB_OAUTH2_CLIENT_SECRET:unset} -export CARBIDE_WEB_PRIVATE_COOKIEJAR_KEY="$(openssl rand -base64 64)" - -export VAULT_ADDR="http://localhost:8200" -export VAULT_KV_MOUNT_LOCATION="secrets" -export VAULT_PKI_MOUNT_LOCATION="certs" -export VAULT_PKI_ROLE_NAME="role" -export VAULT_TOKEN="$(cat /tmp/localdev-docker-vault-root-token)" - -echo "# required variables to run carbide-api:" -printenv | grep -e '^VAULT_' -e '^CARBIDE_' -e DATABASE_URL | sed -e 's/^/export /' -echo "" -echo "# variables on a single line to feed IntelliJ run configuration:" -printenv | grep -e '^VAULT_' -e '^CARBIDE_' | sed -e 's/$/;/' | tr -d '\n' | sed -e 's/;$//' -echo -