diff --git a/learn/developers/deploying-from-ci.mdx b/learn/developers/deploying-from-ci.mdx index 9c91bfe4..7ce7fe65 100644 --- a/learn/developers/deploying-from-ci.mdx +++ b/learn/developers/deploying-from-ci.mdx @@ -50,6 +50,18 @@ A second constraint pushes the same direction: a git-reference deploy authentica A GitHub Actions job that declares `id-token: write` can ask GitHub for an identity token that says which repository, workflow, and environment it is running for. The Harper CLI trades that token for a one-hour operation token, if it matches a **trust policy** you stored on the cluster. Nothing durable lives in your CI provider, and there is no 30-day refresh token to rotate. See [Workload identity (OIDC)](/reference/v5/cli/authentication#workload-identity-oidc) for how the CLI does this, and [`add_oidc_trust`](/reference/v5/operations-api/operations#add_oidc_trust) for every rule a policy can carry. +**Nothing in the workflow names the policy.** Once it exists, every `harper` command in the deploy job — `harper deploy`, `harper get_job`, anything else — authenticates through it by itself, and may do what the policy's user's role allows, as long as the job has `id-token: write`, `HARPER_CLI_TARGET` is the policy's `audience`, the run matches the policy's claims, and no other credential is configured. [Using it in a workflow](/reference/v5/cli/authentication#using-it-in-a-workflow) lists those conditions. + +:::tip + +From v5.4.0, for a workflow that deploys when changes merge to a branch, [`harper deploy setup=true +provider=github-actions`](/reference/v5/cli/commands#github-actions-deploys-oidc) creates a role, a user and a policy +pinned to that branch, and sets the `HARPER_CLI_TARGET` variable, in one command. Its role allows `deploy_component`, +`get_job` and `registration_info` only, not the `get_deployment` the role below adds. This guide's workflows deploy on +version tags, which a branch pin cannot match, so they use the policy below, written by hand. + +::: + You set this up once, from your own machine, logged in as a `super_user`: ```bash @@ -61,13 +73,14 @@ harper login https://my-cluster.example.com:9925 The user a trust policy names is the privilege boundary: a matching run gets that user's role. A pipeline does not need `super_user`. Give it a role that lists only the operations the workflows below call: ```bash -harper add_role role=ci_deploy permission='{"operations":["deploy_component","get_job","get_deployment"]}' +harper add_role role=ci_deploy permission='{"operations":["deploy_component","get_job","get_deployment","registration_info"]}' harper add_user username=ci-deploy role=ci_deploy active=true password="$(openssl rand -base64 32)" ``` - **`deploy_component`** is the deploy itself. - **`get_job`** is what the wait step polls. The jobs table lists `get_job` as open to any role, but an `operations` allowlist refuses every operation it doesn't list, so without it the wait step fails with `403`. - **`get_deployment`** reads the deployment record. +- **`registration_info`** returns only Harper's version, which `harper deploy` checks before it uploads. Without it the deploy still works, but the cluster logs a refused operation on every run. `restart_service` is not needed: the rolling restart's job is started by Harper itself, not by the caller. The password is required to create the user, but a GitHub Actions pipeline never uses it, since it authenticates through the trust policy, so this command discards it. [Other CI systems](#other-ci-systems) shows how to set one you keep. diff --git a/reference/cli/authentication.md b/reference/cli/authentication.md index 25eabfb3..8b56037c 100644 --- a/reference/cli/authentication.md +++ b/reference/cli/authentication.md @@ -261,13 +261,39 @@ There is no operation that revokes a refresh token directly. To invalidate one, On a runner that can prove its own identity, the CLI needs **no stored credential at all**. It asks the runtime for an identity token addressed to your instance and trades it for a one-hour operation token. Nothing durable is stored in your CI provider, and there is no 30-day token to rotate. -Configure the instance to trust the workflow once with [`add_oidc_trust`](../operations-api/operations.md#add_oidc_trust), then grant the token permission in the workflow. +Configure the instance to trust the workflow once with [`add_oidc_trust`](../operations-api/operations.md#add_oidc_trust). For a GitHub Actions workflow that deploys from a branch, [`harper deploy setup=true provider=github-actions`](./commands.md#github-actions-deploys-oidc) (v5.4.0) does it in one command: a deploy-only role and user, the policy, and the repository's `HARPER_CLI_TARGET` variable. -On **GitHub Actions**, a job opts into requesting an identity token with `permissions: id-token: write`. Other CI systems expose the same idea differently, and Harper currently detects GitHub Actions only; an unrecognized runtime is not an error, the CLI simply falls through to its other credential sources. +###### Using it in a workflow -[Deploying from a CI/CD Pipeline](/learn/developers/deploying-from-ci#path-a-deploy-a-tagged-release-from-your-repository) has a complete workflow that runs as written, with the trust policy and deploy-only role it needs. In it, `HARPER_CLI_TARGET` is the only variable the job is given, and it is not sensitive — hence `vars` rather than `secrets`. +**Nothing in the workflow names the policy, and no `harper` command takes an option for it.** Once the policy exists, every `harper` command in a job authenticates through it by itself — `harper deploy`, `harper get_job`, or any other operation — when all four of these hold. What each command may then do is up to the role of the user the policy names: one that lists only `deploy_component`, `get_job` and `registration_info`, like the role setup creates, refuses anything else with a `403`. -**With a trust policy in place you need nothing else** — no `HARPER_CLI_REFRESH_TOKEN`, no password, no credentials on the command. That is the point of it: the workflow holds no Harper secret at all. +1. **The job may request an identity token.** On GitHub Actions that is `permissions: id-token: write` on the job. +2. **`HARPER_CLI_TARGET` is the policy's `audience`.** The CLI requests its token for the target it connects to, so the target must be the URL the policy names, with its port and trailing slash (`https://my-instance.harperdb.io:9925/`). It is not sensitive, so a repository variable (`vars`) rather than a secret. +3. **The run matches the policy's claims**: its repository, its workflow file and branch, and its environment. +4. **Nothing else is configured to authenticate.** `auth_username=`/`auth_password=`, credentials in the target URL, `HARPER_CLI_USERNAME`/`HARPER_CLI_PASSWORD`, `HARPER_CLI_REFRESH_TOKEN` or `HARPER_CLI_OPERATION_TOKEN` (or their `CLI_TARGET_` forms), and a saved `harper login` all outrank the exchange — see [Authentication Precedence](#authentication-precedence). + +For example, this job deploys with nothing but the target: + +```yaml +deploy: + runs-on: ubuntu-latest + environment: production + permissions: + contents: read + id-token: write + env: + HARPER_CLI_TARGET: ${{ vars.HARPER_CLI_TARGET }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - run: npm install --global harper@^5.3 + - run: harper deploy project=my-app restart=rolling +``` + +It is one job, not a whole workflow. [Deploying from a CI/CD Pipeline](/learn/developers/deploying-from-ci#path-a-deploy-a-tagged-release-from-your-repository) has complete workflows that run as written, including the step that waits for a rolling deploy to reach every node. + +Harper currently detects GitHub Actions only. Other CI systems expose the same idea differently; on one Harper does not recognize, the CLI falls through to its other credential sources rather than failing. If your pipeline already sets a token or password, remove it. Leaving it in place is not harmful but it does keep winning — a configured credential outranks the exchange, deliberately, so that enabling this does not silently re-point an existing pipeline at a different user. See [Authentication Precedence](#authentication-precedence) if you need the full order for a mixed setup. diff --git a/reference/cli/commands.md b/reference/cli/commands.md index 4ce79f0a..94919149 100644 --- a/reference/cli/commands.md +++ b/reference/cli/commands.md @@ -166,7 +166,7 @@ All parameters are passed as `key=value` arguments. Every parameter is optional. - `by_ref=true` - Deploy the current project from its GitHub `origin` remote as a pinned commit (`git+https`) instead of uploading a packaged tarball. (Added in: v5.2.3) - `ref=` - The branch, tag, or commit to deploy. Resolved to an immutable commit SHA so every cluster node deploys the same commit. Defaults to the current `HEAD`; implies `by_ref`. (Added in: v5.2.3) - `credential=true` - Attach the sealed credential reference so the cluster can clone a private repository. Provision it first with `harper deploy setup=true`. (Added in: v5.2.3) -- `setup=true` - Provision (seal) a durable encrypted credential for a private deploy source instead of deploying. Interactive. (Added in: v5.2.3) +- `setup=true` - Provision (seal) a durable encrypted credential for a private deploy source instead of deploying; interactive. (Added in: v5.2.3) With `provider=github-actions`, set up [GitHub Actions deploys](#github-actions-deploys-oidc) instead. (Added in: v5.4.0) #### Waiting for a rolling deploy @@ -244,6 +244,38 @@ This interactive flow prompts for the provider (a private GitHub repository or a See [`deploy_component` credentials](../operations-api/operations.md#deploy-credentials-credentials) for the full credential-entry shape, including passing a literal `token` instead of a sealed `secret` reference. +#### GitHub Actions deploys (OIDC) + + + +`harper deploy setup=true provider=github-actions` sets a cluster up so a GitHub Actions workflow can deploy with its [OIDC identity token](./authentication.md#workload-identity-oidc) instead of a stored credential. Run it once, signed in to the cluster as a `super_user` (`harper login`), from the project's checkout: + +```bash +harper deploy setup=true provider=github-actions project=my-app +``` + +The workflow file defaults to `.github/workflows/deploy.yaml`, the name create-harper projects use. If yours has another name, such as `deploy.yml`, pass it with `workflow=`. Otherwise the policy pins `deploy.yaml`, and the runs of your workflow can't match it: + +```bash +harper deploy setup=true provider=github-actions project=my-app workflow=.github/workflows/deploy.yml +``` + +It needs no secret custody, and it does this: + +1. Takes the GitHub repository from the checkout's `origin` remote, or `repo=/`, and looks it up with the `gh` CLI or GitHub's public API, for its numeric id and for the spelling of its name GitHub uses in the token. When neither answers, such as for a private repository without `gh`, pass the id as `repository_id=` and spell `repo=` as GitHub does; setup says the spelling was not verified. +2. Checks the workflow file, `.github/workflows/deploy.yaml` by default (`workflow=`, a `.yml` or `.yaml` file in `.github/workflows/`): it must be able to run on `main` (`branch=`) and run a job in the `production` environment (`environment=`) with `permissions: id-token: write`. A workflow that could never match or never request a token, such as one whose push filter names only other branches and has no other trigger, one in which no job sets an environment, or one whose job in that environment doesn't grant `id-token: write`, stops setup before it writes anything. What it can't decide, such as a branch pattern, a scheduled or manual trigger, or a reusable workflow, is reported as not verified. So is the whole check when the workflow file isn't in the checkout, or the checkout is of another repository (by its `origin` remote, or `GITHUB_REPOSITORY` when that is set): run setup from the repository's own checkout with the workflow committed, or the policy it writes uses the defaults whether or not they match. +3. Creates a role and a user named `-ci-deploy`, allowed only `deploy_component`, `get_job` (to wait for a rolling deploy) and `registration_info` (the version check `harper deploy` makes before it uploads), and the trust policy `github-actions-`. The policy's `audience` is the target exactly as the CLI requests its token, and its claims are the repository id, the workflow and branch (`workflow_ref`), and the environment. +4. Reads the policy back and compares it, claim by claim, with what it meant to write. +5. Sets the repository's `HARPER_CLI_TARGET` variable with `gh`, if the repository doesn't have one. One that already names this cluster is left as it is. One that names another cluster is left too, since every workflow in the repository that reads it deploys there: setup prints the command to change it and exits non-zero. Without `gh`, or if the variable can't be read or written, it prints the command to run and exits non-zero. + +It only creates. Running it again leaves the cluster and the variable unchanged, and a role, user or policy that already exists but differs, or that someone deactivated or disabled, stops it before it writes, naming what differs. To replace a policy, drop it with `harper drop_oidc_trust id=github-actions-` and run setup again. Because `add_oidc_trust` replaces a policy, setup checks again just before writing it, but a policy another super user creates or disables in that moment would still be overwritten. + +If the variable wasn't set, the cluster part is already done: run the printed `gh variable set` command, or run setup again, which leaves the cluster as it is and tries the variable again. + +The deploy user can deploy any component on the cluster, not only this project, so who can deploy comes down to who can merge to the branch. To revoke, `harper drop_oidc_trust id=github-actions-` stops new runs, and `harper alter_user username=-ci-deploy active=false` also stops a token already issued, which lasts an hour. + +The policy pins one branch, so it suits a workflow that deploys when changes merge. A workflow that deploys on tags needs a policy written by hand: see [Deploying from a CI/CD Pipeline](/learn/developers/deploying-from-ci#add-a-trust-policy). + ### `harper restart` Available since: v4.1.0 diff --git a/reference/cli/operations-api-commands.md b/reference/cli/operations-api-commands.md index 229a34d6..ab5a431a 100644 --- a/reference/cli/operations-api-commands.md +++ b/reference/cli/operations-api-commands.md @@ -93,7 +93,7 @@ This is just a brief overview of all operations available as CLI commands. Revie | `restart_service` | Restart Harper service | [System](../operations-api/operations.md#registration--licensing) | v4.3.0 | | `restart` | Restart Harper instance | [System](../operations-api/operations.md#registration--licensing) | v4.3.0 | | `system_information` | Get system information | [System](../operations-api/operations.md#registration--licensing) | v4.3.0 | -| `registration_info` | Get registration information | [Licensing](../operations-api/operations.md#registration--licensing) | v4.3.0 | +| `registration_info` | Get the Harper version | [Licensing](../operations-api/operations.md#registration--licensing) | v4.3.0 | | `get_fingerprint` | Get instance fingerprint | [Licensing](../operations-api/operations.md#registration--licensing) | v4.3.0 | | `set_license` | Set license key | [Licensing](../operations-api/operations.md#registration--licensing) | v4.3.0 | | `get_usage_licenses` | Get usage and license info | [Licensing](../operations-api/operations.md#registration--licensing) | v4.7.3 | diff --git a/reference/operations-api/operations.md b/reference/operations-api/operations.md index 23e1e330..e0dacb11 100644 --- a/reference/operations-api/operations.md +++ b/reference/operations-api/operations.md @@ -620,7 +620,7 @@ A CI runner can authenticate to Harper with **no stored credential**. It present The alternative is a `HARPER_CLI_REFRESH_TOKEN` secret: a 30-day credential, one per user, that expires on a schedule nobody tracks. A trust policy replaces it with a rule you configure once, and revoke with `drop_oidc_trust`. -The workflow needs `permissions: id-token: write` and the `HARPER_CLI_TARGET` variable, and no secret at all, since the target is not sensitive. [Deploying from a CI/CD Pipeline](/learn/developers/deploying-from-ci#let-the-workflow-authenticate-with-oidc) has a complete workflow, with the trust policy and deploy-only role it needs. See [CLI Authentication](../cli/authentication.md#workload-identity-oidc) for the client half and where the exchange sits in credential precedence. +The workflow needs `permissions: id-token: write` and the `HARPER_CLI_TARGET` variable, and no secret at all, since the target is not sensitive. Nothing in it names the policy: every `harper` command in the job authenticates through it by itself, and may do what the policy's user's role allows — see [Using it in a workflow](../cli/authentication.md#using-it-in-a-workflow). For a workflow that deploys from a branch, [`harper deploy setup=true provider=github-actions`](../cli/commands.md#github-actions-deploys-oidc) (v5.4.0) creates a deploy-only role and user and the policy, and sets the repository's `HARPER_CLI_TARGET` variable, in one command. [Deploying from a CI/CD Pipeline](/learn/developers/deploying-from-ci#let-the-workflow-authenticate-with-oidc) has a complete workflow, with the trust policy and deploy-only role it needs. See [CLI Authentication](../cli/authentication.md#workload-identity-oidc) for the client half and where the exchange sits in credential precedence. Policies live in the replicated `system.hdb_oidc_trust` table, so configuring one on any node applies cluster-wide. @@ -674,7 +674,7 @@ harper add_oidc_trust \ harper list_oidc_trust ``` -`claims` is a JSON object, so quote it as a single shell argument — the CLI parses each value as JSON, which is what turns that string into the nested object the operation expects. `operations` works the same way if you scope the policy: `operations='["deploy_component","get_job","get_deployment"]'`. +`claims` is a JSON object, so quote it as a single shell argument — the CLI parses each value as JSON, which is what turns that string into the nested object the operation expects. `operations` works the same way if you scope the policy: `operations='["deploy_component","get_job","get_deployment","registration_info"]'`. `harper login` stores a token for that target, so step 2 needs no credentials of its own. If you would rather not store one, pass the target and credentials explicitly instead: @@ -697,7 +697,7 @@ The user the policy names is the privilege boundary: a matching run gets that us "issuer": "https://token.actions.githubusercontent.com", "audience": "https://my-instance.harperdb.io:9925/", "user": "ci-deploy", - "operations": ["deploy_component", "get_job", "get_deployment"], + "operations": ["deploy_component", "get_job", "get_deployment", "registration_info"], "claims": { "repository_id": "67890", "workflow_ref": "HarperFast/my-app/.github/workflows/deploy.yml@refs/heads/main", @@ -723,12 +723,12 @@ A deploy user does not need `super_user`. A role that lists the operations the p "operation": "add_role", "role": "ci_deploy", "permission": { - "operations": ["deploy_component", "get_job", "get_deployment"] + "operations": ["deploy_component", "get_job", "get_deployment", "registration_info"] } } ``` -`get_job` is what a pipeline polls to wait for a `"restart": "rolling"` deploy to reach every node, and `get_deployment` reads the deployment record. Both have to be listed: an `operations` allowlist, and a policy's `operations` scope, refuse every operation they don't name, including ones like `get_job` that any role may otherwise call. `restart_service` is not needed, since Harper starts the rolling restart's job itself. +`get_job` is what a pipeline polls to wait for a `"restart": "rolling"` deploy to reach every node, `get_deployment` reads the deployment record, and `registration_info` is the version check `harper deploy` makes before it uploads; without that one the deploy still works, but the node logs a refused operation on every run. Each has to be listed: an `operations` allowlist, and a policy's `operations` scope, refuse every operation they don't name, including ones like `get_job` that any role may otherwise call. `restart_service` is not needed, since Harper starts the rolling restart's job itself. Deploying is still administrative authority, since the deployed component runs inside the Harper process. A deploy that passes a literal registry or git `token` in `credentials` needs `super_user` on a node that holds secret custody, because Harper seals that token into the secrets store. Give a least-privilege role a `secret` reference instead. @@ -2097,7 +2097,7 @@ Operations for license management. | Operation | Description | Role Required | | ----------------------- | -------------------------------------------------- | ------------- | -| `registration_info` | Returns registration and version information | any | +| `registration_info` | Returns the Harper version | any | | `install_usage_license` | Installs a Harper usage license block | super_user | | `get_usage_licenses` | Returns all usage licenses with consumption counts | super_user | | `get_fingerprint` | _(Deprecated)_ Returns the machine fingerprint | super_user | @@ -2105,12 +2105,16 @@ Operations for license management. ### `registration_info` -Returns the instance registration status, version, RAM allocation, and license expiration. +Returns the instance's Harper version, and `deprecated: true`. It no longer returns registration details. `harper deploy` calls it to check the target's version before it uploads, so a role with an `operations` allowlist that runs `harper deploy` should list it. ```json { "operation": "registration_info" } ``` +```json +{ "version": "5.3.2", "deprecated": true } +``` + ### `install_usage_license` Installs a usage license block. A license is a JWT-like structure (`header.payload.signature`) signed by Harper. Multiple blocks may be installed; earliest blocks are consumed first. @@ -2161,7 +2165,7 @@ For modern equivalents, see [Components Overview](../components/overview.md). | Operation | Replaced By | | ---------------------- | ------------------------------------------------------------------- | | `install_node_modules` | Handled automatically by `deploy_component` and `restart` | -| `get_fingerprint` | Use `registration_info` | +| `get_fingerprint` | None: `registration_info` returns only the version | | `set_license` | Use `install_usage_license` | | `search_by_hash` | Use `search_by_id` | | `search_attribute` | Use `attribute` field in `search_by_value` / `search_by_conditions` | diff --git a/release-notes/v5-lincoln/5.4.md b/release-notes/v5-lincoln/5.4.md index 32120367..ea17163f 100644 --- a/release-notes/v5-lincoln/5.4.md +++ b/release-notes/v5-lincoln/5.4.md @@ -23,3 +23,7 @@ A durable MQTT session now resumes through the same checked replay. When a recon ### Certifying a Release in a Canary Worker A `deploy_component` with `restart: true` or `restart: "rolling"` now checks its release before rolling it out. The first worker started on the release, the canary, is held out of traffic until it reports whether the deployed component loaded, and the rollout goes on only if it did. Each later replacement is checked the same way. When the node that received the deploy rejects the release, the deploy fails with `400`. The release it replaced is made live again, or, with nothing to go back to, the component fails closed on that node. A new `certification` field in the response reports the outcome, and says when a restarting deploy went out unchecked (`uncertified` or `unavailable`, as for a `package: file:` deploy). With `"rolling"`, each other node then certifies the release with a canary of its own, and a node that rejects it fails the deploy's job, not the deploy: poll its `restartJobId` with `get_job` for the outcome. A node running a version before 5.4.0 activates without a canary. A deploy that restarts nothing is not test-loaded. If its release throws when it loads, `get_status` reports the failure. See [Certifying a release in a canary worker](/reference/v5/operations-api/operations#certifying-a-release-in-a-canary-worker). + +### Setting Up GitHub Actions Deploys + +`harper deploy setup=true provider=github-actions` sets a cluster up so a GitHub Actions workflow can deploy with its OIDC identity token instead of a stored credential. It creates a deploy-only role and user, a trust policy matching the workflow's repository, branch and environment, and the repository's `HARPER_CLI_TARGET` variable. It checks the policy against the workflow file in the repository's checkout before it writes, where it can, and reads it back after. It only creates: running it again leaves the cluster and the variable as they are, a record that differs, or that someone deactivated or disabled, stops it before it writes, and a `HARPER_CLI_TARGET` that names another cluster is left in place. A policy another super user creates or disables in the moment before setup writes it is still overwritten. See [GitHub Actions deploys (OIDC)](/reference/v5/cli/commands#github-actions-deploys-oidc).