diff --git a/.github/workflows/automatic-doc-checks.yml b/.github/workflows/automatic-doc-checks.yml index 6b7d9cf1..95dca1a4 100644 --- a/.github/workflows/automatic-doc-checks.yml +++ b/.github/workflows/automatic-doc-checks.yml @@ -5,13 +5,11 @@ on: push: branches: [ main ] pull_request: - paths: - - 'docs/**' # Only run on changes to the docs directory - workflow_call: workflow_dispatch: # Manual trigger + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true @@ -21,4 +19,4 @@ jobs: uses: canonical/documentation-workflows/.github/workflows/documentation-checks.yaml@main with: working-directory: "docs" - fetch-depth: 0 + fetch-depth: 0 \ No newline at end of file diff --git a/.github/workflows/check-removed-urls.yml b/.github/workflows/check-removed-urls.yml deleted file mode 100644 index cfb6b4d4..00000000 --- a/.github/workflows/check-removed-urls.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Check for removed URLs - -on: - workflow_call: - pull_request: - branches: [main] - -jobs: - build-docs: - runs-on: ubuntu-latest - steps: - - name: Checkout PR branch - uses: actions/checkout@v5 - with: - # This implicitly gets the PR branch. Making it explicit causes problems - # with private forks, but it is equivalent to the following: - # repository: ${{ github.event.pull_request.head.repo.full_name }} - # ref: ${{ github.event.pull_request.head.ref }} - fetch-depth: 0 - path: compare - - name: Checkout base branch - uses: actions/checkout@v5 - with: - ref: ${{ github.event.pull_request.base.ref }} - repository: ${{ github.event.pull_request.base.repo.full_name }} - fetch-depth: 0 - path: base - - uses: actions/setup-python@v6 - - name: Build docs - run: | - for dir in compare base; do - pushd ${dir}/docs - make install - . .sphinx/venv/bin/activate - make html - popd - done - - name: Generate current URLs list - run: | - for dir in compare base; do - pushd ${dir}/docs - find ./_build/ -name '*.html' \ - | sed 's|/_build||;s|/index.html$|/|;s|.html$||' \ - | sort > urls.txt - popd - done - - name: Compare URLs - run: | - BASE_URLS_PATH="base/docs/urls.txt" - COMPARE_URLS_PATH="compare/docs/urls.txt" - removed=$(comm -23 ${BASE_URLS_PATH} ${COMPARE_URLS_PATH} ) - if [ -n "$removed" ]; then - echo "The following URLs were removed:" - echo "$removed" - echo "Please ensure removed pages are redirected" - exit 1 - fi diff --git a/.github/workflows/markdown-style-checks.yml b/.github/workflows/markdown-style-checks.yml index e288d005..d801f99a 100644 --- a/.github/workflows/markdown-style-checks.yml +++ b/.github/workflows/markdown-style-checks.yml @@ -1,28 +1,20 @@ name: Markdown style checks on: - workflow_call: push: branches: - main - paths: - - 'docs/**' # Only run on changes to the docs directory pull_request: branches: - '*' - paths: - - 'docs/**' # Only run on changes to the docs directory jobs: markdown-lint: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Create venv - working-directory: "docs" - run: make install - - name: Lint markdown - working-directory: "docs" - run: make lint-md + - uses: DavidAnson/markdownlint-cli2-action@v16 + with: + config: ".sphinx/.markdownlint.json" \ No newline at end of file diff --git a/docs/.custom_wordlist.txt b/docs/.custom_wordlist.txt index 533f4115..94680f77 100644 --- a/docs/.custom_wordlist.txt +++ b/docs/.custom_wordlist.txt @@ -50,6 +50,7 @@ ceph Certbot certbot centralized +cfg Charmhub Charmhub's changelog @@ -64,7 +65,11 @@ CLI's Cognito conf config +configurator +configurators +Configurator configs +Configurator conformant conntrack copiedprofilename @@ -104,8 +109,10 @@ Entra EPMD epoll ESM +FDE eu falsy +FDERecoveryKeyManager Fernet FIPS FQDN @@ -125,6 +132,7 @@ grpcio filesystem frontend HAProxy +haproxy HashiCorp hardcoded HashiCorp’s @@ -149,7 +157,9 @@ init IoT Iot ip +IPs IPv +Jinja jQuery Juju's Juju @@ -164,15 +174,19 @@ keyslots Kudu KVM keyrings +keyslot kvm kwarg labelled launchpadcontent +lbaas +LBaaS LDAP LDS LetsEncrypt libvirt licence +lego lifecycle Livepatch Livepatches @@ -187,6 +201,7 @@ LXD lxd MAAS maas +maxconn Mailjet Makefile ManualInstallation @@ -242,9 +257,11 @@ queueingQuickstartquickstart packagesearch PAYG PCI +pem perf perfs performant +PID PIDs PITR pgSQL @@ -252,6 +269,8 @@ pgtune pingserver pluggable png +pooler +poolers Postconf postgres PostgreSQL @@ -339,6 +358,8 @@ timeframe TLD TLS TLSv +tmpfiles +TPM traceback transactional truthy diff --git a/docs/conf.py b/docs/conf.py index aeb112a9..2f1fa70e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -245,7 +245,8 @@ "sphinx_last_updated_by_git", "sphinx.ext.intersphinx", "sphinx_sitemap", - "sphinxext.rediraffe" + "sphinxext.rediraffe", + "sphinxcontrib.mermaid", ] @@ -334,7 +335,7 @@ rediraffe_branch = "main" rediraffe_redirects = "redirects.txt" -## Reredirects extension +## Reredirects extension ## NOTE: We've moved to use rediraffe as our main redirects extension. ## The following are ones we already had in place, but haven't migrated redirects = { @@ -344,4 +345,4 @@ 'how-to-guides/security/install-landscape-in-an-air-gapped-or-offline-environment': '../../landscape-installation-and-set-up/install-landscape-in-an-air-gapped-or-offline-environment', 'explanation/repository-mirroring/repository-mirroring': '../../features/repository-mirroring', 'reference/known-issues/known-issues': '../../known-issues' -} \ No newline at end of file +} diff --git a/docs/explanation/charm/charm-compatibility.md b/docs/explanation/charm/charm-compatibility.md index db3dd65c..b738eeec 100644 --- a/docs/explanation/charm/charm-compatibility.md +++ b/docs/explanation/charm/charm-compatibility.md @@ -8,38 +8,136 @@ myst: # Landscape Server charm integration compatibility -The [Landscape Server charm](https://charmhub.io/landscape-server) can be deployed with Juju when integrated with at least the following charms: +The [Landscape Server charm](https://charmhub.io/landscape-server) requires integration with the following charms, depending on the version: -- `haproxy` +**All versions:** - `postgresql` - `rabbitmq-server` -For a recommended charm bundle configuration and more information about deploying Landscape Server with Juju, see {ref}`How to install Landscape Server with Juju `. +**Landscape 26.04 LTS beta+:** +- `haproxy` (at `2.8/edge`, via `haproxy-route` interface) +- A TLS certificates provider integrated with HAProxy (e.g., `self-signed-certificates`, `lego`) -Compatibility with a Landscape Server charm revision is limited to specific revisions and channels of the required charms. Incompatibility can occur if the charm’s base (e.g., `ubuntu@24.04`), architecture (e.g., `amd64`), or interfaces change. +**Before Landscape 26.04:** +- `haproxy` (via `reverseproxy` interface) + +For a recommended charm bundle configuration and more information about deploying Landscape Server with Juju, see {ref}`how-to-juju-installation` and {ref}`how-to-juju-ha-installation`. + +Compatibility with a Landscape Server charm revision is limited to specific revisions and channels of the required charms. Incompatibility can occur if the charm's base (e.g., `ubuntu@24.04`), architecture (e.g., `amd64`), or interfaces change. ```{tip} Learn more about [Juju integrations](https://canonical.com/juju/integrations). ``` +## Deployment architectures + +The Landscape Server charm supports two deployment architectures: + +**Landscape 26.04 LTS beta+ (recommended):** +- External HAProxy charm (`2.8/edge`) for load balancing, using the `haproxy-route` interface +- PostgreSQL 14+ with modern `database` interface +- TLS certificates via a `tls-certificates` interface provider integrated with HAProxy + +```{include} /reuse/charm-ha-architecture-2604.md +``` + +**Before 26.04:** +- External HAProxy charm for load balancing, using the `reverseproxy` interface +- PostgreSQL ≥ 14 with legacy `pgsql` interface + +```{include} /reuse/charm-ha-architecture-pre-2604.md +``` + +For migration from older deployments to 26.04 beta+, see {ref}`how-to-migrate-to-26-04-charm`. + +## Required integrations by version + +| Charm | Landscape 26.04 LTS beta+ | Before 26.04 | +| ----------------------------- | -------------------------------------------------------------------- | ----------------------------------------------- | +| **PostgreSQL** | Required (PostgreSQL 14+, `database` interface) | Required (PostgreSQL 14, `pgsql` interface) | +| **RabbitMQ Server** | Required | Required | +| **HAProxy** | Required (`2.8/x`, `haproxy-route` interface) | Required (`latest/x`, `reverseproxy` interface) | +| **TLS Certificates Provider** | Required (integrated with HAProxy, e.g., `self-signed-certificates`) | Not used | + +## TLS certificates charm interface + +Starting with the 26.04 beta version, TLS is managed by the HAProxy charm. The HAProxy charm integrates with a provider of the [`tls-certificates` charm interface](https://charmhub.io/integrations/tls-certificates) to obtain certificates for HTTPS connections. + +### Available TLS certificate providers + +**For testing/development:** +- [`self-signed-certificates`](https://charmhub.io/self-signed-certificates) - Generates self-signed certificates (not trusted by browsers/clients) + +**For production:** +- [`lego`](https://charmhub.io/lego) - Obtains certificates from Let's Encrypt or other ACME providers +- [`manual-tls-certificates`](https://charmhub.io/manual-tls-certificates) - Use custom CA certificates +- Any charm that provides the `tls-certificates` interface + +Integrate TLS with HAProxy (not with `landscape-server` directly): + +```bash +juju integrate haproxy:certificates :certificates +``` + +For deployment examples and configuration, see {ref}`how-to-juju-ha-installation` and {ref}`how-to-migrate-to-26-04-charm`. + ## K8s Operators Landscape Server is currently only distributed as a machine (VM) charm and cannot be directly integrated with any version of K8s Charmed Operators, such as the HAProxy K8s operator or the Charmed PostgreSQL K8s operator. ## HAProxy -The Landscape Server charm can be integrated to any of the `latest/x` channels of the HAProxy charm. However, it cannot yet be related to any of the `2.8/x` channels of the HAProxy charm due to a complete rewrite of the charm and its interfaces. +The relationship between Landscape Server and HAProxy varies significantly between Landscape versions: + +**Landscape 26.04 LTS beta+:** +- Requires the HAProxy charm at `2.8/edge` +- Integrates via 8 `haproxy-route` relation endpoints directly from landscape-server to haproxy +- HAProxy handles TLS termination and load balancing +- Cannot be integrated with the `latest/x` channels of the HAProxy charm (different interface) + +**Before 26.04:** +- Requires the external HAProxy charm at `latest/x` +- Integrates via the `reverseproxy` interface: `landscape-server:website` → `haproxy:reverseproxy` +- Cannot be integrated with the `2.8/x` channels of the HAProxy charm + +**LBaaS (Load Balancer as a Service) - cross-model HAProxy:** +- Deploy HAProxy in a separate Juju model and use cross-model relations +- Landscape Server integrates directly with the cross-model HAProxy via `haproxy-route` endpoints +- See {ref}`heading-lbaas-installation` for complete setup + +For migrating from older deployments to the new HAProxy architecture, see {ref}`how-to-migrate-to-26-04-charm`. - [HAProxy on Charmhub](https://charmhub.io/haproxy) ## Charmed PostgreSQL -The Landscape Server charm can be integrated to any of the `latest/x` or `14/x` channels of the PostgreSQL charm. However, it cannot yet be related to any of the `16/x` channels of the PostgreSQL charm due to the charm interface being restructured. +PostgreSQL charm compatibility varies by Landscape Server version: + +**Landscape 26.04 LTS beta+:** +- Compatible with PostgreSQL 14+ using the modern `database` interface +- Landscape Server integrates using the `database` relation endpoint: `landscape-server:database` → `postgresql:database` +- It is recommended to use PostgreSQL 16 for new deployments +- Supports [PgBouncer](https://charmhub.io/pgbouncer) as a connection pooler via the `database` relation endpoint; see {ref}`explanation-pgbouncer-integration` + +**Before 26.04:** +- Compatible with PostgreSQL 14 using the legacy `pgsql` interface +- Landscape Server integrates using the `db` relation endpoint: `landscape-server:db` → `postgresql:db-admin` +- Cannot use PostgreSQL 16 due to interface incompatibility - [Charmed PostgreSQL VM on Charmhub](https://charmhub.io/postgresql) ## RabbitMQ Server -There are no charm integration incompatibilities between the Landscape Server charm and the RabbitMQ Server charm. +RabbitMQ Server integration varies by Landscape Server version: + +**Landscape 25.10+:** +- Uses separate inbound and outbound AMQP relation endpoints +- Relations: + - `landscape-server:inbound-amqp` ↔ `rabbitmq-server` + - `landscape-server:outbound-amqp` ↔ `rabbitmq-server` + +**Before 25.10:** +- Uses a single `amqp` relation endpoint +- Relation: `landscape-server:amqp` ↔ `rabbitmq-server:amqp` - [RabbitMQ Server on Charmhub](https://charmhub.io/rabbitmq-server) diff --git a/docs/explanation/charm/index.md b/docs/explanation/charm/index.md index b78e7628..d873394d 100644 --- a/docs/explanation/charm/index.md +++ b/docs/explanation/charm/index.md @@ -1,7 +1,7 @@ --- myst: html_meta: - description: "Learn about Landscape Server charm for Juju deployments including integration compatibility with HAProxy, PostgreSQL, and RabbitMQ charms." + description: "Learn about Landscape Server charm for Juju deployments including integration compatibility with HAProxy, PostgreSQL, RabbitMQ, and PgBouncer." --- (explanation-charm-index)= @@ -15,3 +15,5 @@ These are the explanations for the [Landscape Server charm](https://charmhub.io/ :maxdepth: 2 Charm integration compatibility +PgBouncer integration +``` diff --git a/docs/explanation/charm/pgbouncer-integration.md b/docs/explanation/charm/pgbouncer-integration.md new file mode 100644 index 00000000..37beab73 --- /dev/null +++ b/docs/explanation/charm/pgbouncer-integration.md @@ -0,0 +1,83 @@ +--- +myst: + html_meta: + description: "Understanding PgBouncer integration with Landscape Server charm, including the postgresql_client interface requirement and compatibility considerations." +--- + +(explanation-pgbouncer-integration)= +# PgBouncer integration with Landscape Server + +PgBouncer is a lightweight connection pooler for PostgreSQL that can significantly improve database performance and scalability in Landscape Server deployments. By pooling and reusing database connections, PgBouncer reduces the overhead of establishing new connections and allows your deployment to handle more concurrent operations efficiently. + +PgBouncer is available for Landscape Server charmed deployments. + +There are some benefits and trade-offs to consider if implementing PgBouncer. + +## Benefits + +- **Reduced connection overhead**: Reusing connections eliminates the cost of repeatedly establishing new database connections +- **Lower memory footprint**: Fewer active connections to PostgreSQL means lower memory usage on the database server +- **Improved concurrency**: More clients can be served with the same database resources +- **Connection limits**: Helps manage PostgreSQL's `max_connections` limit by multiplexing many client connections through fewer backend connections +- **Query performance**: Better query throughput through efficient connection management + +## Trade-offs + +- **Additional component**: Introduces another service to deploy, monitor, and maintain +- **Compatibility requirement**: Requires a recent Landscape Server charm revision with `postgresql_client` interface support +- **Debugging complexity**: Connection issues may require examining both PgBouncer and PostgreSQL logs +- **Configuration overhead**: Requires understanding PgBouncer pooling modes and parameters for optimal performance + +## When to use PgBouncer + +PgBouncer is recommended for: + +- **Production deployments** with significant client loads +- **High-availability configurations** where connection management is critical +- **Large-scale environments** managing hundreds or thousands of clients +- **Resource-constrained setups** where database connection limits are a concern + +For small test deployments or proof-of-concept environments with minimal client loads, direct PostgreSQL connections are often be sufficient. + +## Connection architecture + +When deploying with PgBouncer, the connection architecture changes from a direct connection to a pooled connection pattern: + +**Without PgBouncer:** +``` +Landscape Server → PostgreSQL +``` + +**With PgBouncer:** +``` +Landscape Server → PgBouncer → PostgreSQL +``` + +PgBouncer sits between Landscape Server and PostgreSQL, managing a pool of connections to the database and efficiently distributing requests across the connection pool. + +## The `postgresql_client` interface + +Integration with PgBouncer requires the Landscape Server charm to use the `database` relation endpoint, which uses the `postgresql_client` charm interface under the hood. This contrasts with the legacy `db` endpoint and `pgsql` interface used for direct PostgreSQL connections without a pooler. + +```{important} +Only recent revisions of the Landscape Server charm support the `postgresql_client` interface required for PgBouncer integration. See {ref}`explanation-charm-compatibility` for details on which charm revisions support this integration. +``` + +## Juju relation pattern + +In a Juju deployment with PgBouncer, the relations are configured as follows: + +```yaml +relations: + - [landscape-server:database, pgbouncer:database] + - [pgbouncer:backend-database, postgresql:database] +``` + +The Landscape Server charm relates to PgBouncer using the `database` endpoint, and PgBouncer relates to PostgreSQL using its `backend-database` endpoint. This creates the connection pooling layer between the application and the database. + +## See also + +- {ref}`explanation-charm-compatibility` - Charm revision compatibility information +- {ref}`how-to-juju-ha-installation` - High-availability Landscape deployment guide +- [PgBouncer charm documentation](https://charmhub.io/pgbouncer) +- [Charmed PostgreSQL documentation](https://charmhub.io/postgresql) diff --git a/docs/explanation/features/activities.md b/docs/explanation/features/activities.md index 4ec6c55b..51c16e6a 100644 --- a/docs/explanation/features/activities.md +++ b/docs/explanation/features/activities.md @@ -5,6 +5,7 @@ myst: --- (explanation-activities)= + # Activities Landscape tracks the progress of various tasks using activities, such as script execution and installing packages. There are two types of activities: client activities and server activities. This document explains how each type of activity is tracked and their possible states. @@ -102,3 +103,4 @@ Activities can start in the `Queued`, `Scheduled`, `Waiting`, or `Blocked` state - `RemoveComputerActivity` - `AttachProRequest` - `DetachProRequest` +- `GenerateFDERecoveryKeyRequest` diff --git a/docs/explanation/features/fde-recovery-key.md b/docs/explanation/features/fde-recovery-key.md new file mode 100644 index 00000000..0e41378b --- /dev/null +++ b/docs/explanation/features/fde-recovery-key.md @@ -0,0 +1,73 @@ +(explanation-fde-recovery-key)= + +# Full Disk Encryption (FDE) recovery keys + +Landscape Server provides a centralized way to request, store, and manage recovery keys for managed instances. This document describes how Landscape Server requests Landscape Client to generate a recovery key via the snapd API. + +FDE recovery keys can be generated for target instances that meet the following requirements: + +- TPM-backed Full Disk Encryption must be enabled. +- snapd version `2.74` or higher. +- Landscape Client version `26.04` or higher with the `FDERecoveryKeyManager` plugin enabled. + +## Overview + +The recovery key generation process is handled via Landscape activities. See {ref}`explanation-activities` for details on how activities are delivered to clients. + +## Key request (Landscape Server -> Landscape Client) + +When an administrator requests a new or replacement recovery key for an instance, Landscape Server creates a `GenerateFDERecoveryKeyRequest` activity. Landscape Server sends a `fde-recovery-key` message to Landscape Client in the following form: + +```json +{ + "type": "fde-recovery-key", + "operation-id": "", + "action": "generate", +} +``` + +Field descriptions: + +- `operation-id`: Unique identifier of the activity. +- `action`: The operation to perform. Currently `generate` is the only supported value. + +## Landscape Client execution + +Once Landscape Client receives the `fde-recovery-key` message, it executes the following steps via snapd API calls: + +1. Check to see if a `landscape-recovery-key` keyslot exists. + - If found, Landscape Client prepares to replace the existing keyslot. + - If not found, Landscape Client prepares to add a new keyslot. +1. Generate the recovery key. +1. Add or replace the `landscape-recovery-key` keyslot using the new recovery key. +1. Store the recovery key temporarily in memory to be included in the response message back to Landscape Server. + +```{note} +The recovery key is not saved to disk during this process. If Landscape Client exits before reporting to Server, the recovery key will be lost and the administrator should request a new key. +``` + +## Key reporting (Landscape Client -> Landscape Server) + +Once the execution is complete, Landscape Client sends a `fde-recovery-key` message to Landscape Server in the following form: + +```json +{ + "operation-id": "", + "recovery-key": "", + "result-text": "", + "successful": true, +} +``` + +Field descriptions: + +- `operation-id`: Unique identifier of the activity. +- `recovery-key`: Recovery key which can be used to unlock the encrypted disk. Can be omitted if the recovery key generation failed. +- `result-text`: If the activity failed, this will have the error message. Otherwise, it will be "Generated new FDE recovery key." +- `successful`: If True, the recovery key generation was successful. Landscape Client uses this to determine if the recovery key in-memory is accurate. + +## Landscape Server storage + +Once Landscape Server receives the recovery key, it adds the recovery key to the Landscape Server Secrets Storage. + +If the computer related to a recovery key is removed from Landscape, the recovery key is also removed from the Secrets Storage. diff --git a/docs/explanation/features/index.md b/docs/explanation/features/index.md index d3180223..ba326594 100644 --- a/docs/explanation/features/index.md +++ b/docs/explanation/features/index.md @@ -43,3 +43,14 @@ Run scripts on managed machines remotely, enabling centralized automation across Remote script execution ``` + +## Security + +Understand security-related Landscape features such as Full Disk Encryption. + +```{toctree} +:titlesonly: +:maxdepth: 2 + +Full Disk Encryption recovery keys + diff --git a/docs/how-to-guides/api/make-a-rest-api-request.md b/docs/how-to-guides/api/make-a-rest-api-request.md index b4b580ed..a8fd983c 100644 --- a/docs/how-to-guides/api/make-a-rest-api-request.md +++ b/docs/how-to-guides/api/make-a-rest-api-request.md @@ -11,6 +11,8 @@ myst: This guide demonstrates how to login and make a request using the Landscape REST API. You need Landscape 24.04 LTS (or higher) to use the REST API. This guide provides general steps and examples using `curl`. +> **Note**: When using state-changing methods (i.e. `POST`, `PUT`, and `PATCH`), you must explicitly declare `Content-Type` as `application/json`. + ## Make a REST API request with your preferred API tool The general steps to make a REST API request are: @@ -29,7 +31,9 @@ This method is for those using password authentication to login. 1. Provide your login credentials with `POST`. Your request will be similar to the following ```bash - curl -X POST "https://your-landscape.domain.com/api/v2/login" -d '{"email": "jane@example.com", "password": "jane-pwd", "account": "standalone"}' + curl -X POST "https://your-landscape.domain.com/api/v2/login" \ + -H "Content-Type: application/json" \ + -d '{"email": "jane@example.com", "password": "jane-pwd", "account": "standalone"}' ``` And you’ll receive output similar to: @@ -78,8 +82,8 @@ To make a REST API request with `curl`: ```bash curl -X POST https://your-landscape.domain.com/api/v2/login/access-key \ - -H "Content-Type: application/json" \ - -d '{"access_key": "3AS5YX98J8QI9AZ8OS0V", "secret_key": "avlhg23w9HyOWOA1FMzHmrBaB8a97zafzJOApfF2"}' + -H "Content-Type: application/json" \ + -d '{"access_key": "3AS5YX98J8QI9AZ8OS0V", "secret_key": "avlhg23w9HyOWOA1FMzHmrBaB8a97zafzJOApfF2"}' ``` And you’ll receive output similar to diff --git a/docs/how-to-guides/api/use-the-legacy-api-via-http-requests.md b/docs/how-to-guides/api/use-the-legacy-api-via-http-requests.md index 2204ec57..a9077fba 100644 --- a/docs/how-to-guides/api/use-the-legacy-api-via-http-requests.md +++ b/docs/how-to-guides/api/use-the-legacy-api-via-http-requests.md @@ -38,7 +38,7 @@ Here's an example request: 1. Obtain a JWT: ```bash - TOKEN=$(curl -s -X POST "https:///api/login" \ + JWT=$(curl -s -X POST "https:///api/login" \ -H "Content-Type: application/json" \ -d '{"email": "", "password": ""}' | jq -r '.token') ``` @@ -47,7 +47,7 @@ Here's an example request: ```bash curl -X GET "https:///api/?action=GetComputers&version=" \ - -H "Authorization: Bearer $TOKEN" + -H "Authorization: Bearer $JWT" ``` > **Note**: The `version` parameter is mandatory in the URL for the legacy API. Without it, the request will fail. diff --git a/docs/how-to-guides/backup-and-maintenance/manage-maintenance-cron-jobs.md b/docs/how-to-guides/backup-and-maintenance/manage-maintenance-cron-jobs.md index 16763e3d..b2ffea0d 100644 --- a/docs/how-to-guides/backup-and-maintenance/manage-maintenance-cron-jobs.md +++ b/docs/how-to-guides/backup-and-maintenance/manage-maintenance-cron-jobs.md @@ -32,6 +32,8 @@ The following are automatically scheduled cron jobs. These scripts are all locat - `report_anonymous_metrics.sh` (Landscape 25.04 and earlier) - Reports anonymous metrics (e.g., the installed Landscape Server version). This job was removed in Landscape 25.08. +These scripts depend on a PID file located in `/run/landscape/` to prevent multiple instances of a script from running concurrently. This directory is created at boot by a systemd tmpfiles configuration located at `/etc/tmpfiles.d/landscape-server-tmpfile.conf`. + ## Optional Cleaning of Activity History Landscape includes some basic cleanup tasks in the `maintenance.sh` job. You may want to create additional jobs to limit old activities and events to a defined retention period. diff --git a/docs/how-to-guides/iot-for-devices/index.md b/docs/how-to-guides/iot-for-devices/index.md index 3ec9cf2b..e9c8a076 100644 --- a/docs/how-to-guides/iot-for-devices/index.md +++ b/docs/how-to-guides/iot-for-devices/index.md @@ -49,6 +49,8 @@ IoT devices come with their own unique characteristics and challenges. Therefore - Use groups and tags to control your updates - Deploy to test groups first to ensure functionality after upgrade - Choose regional rollout strategies to match local maintenance windows +- **Use validation sets to control updates to snaps** + - We recommend validation sets as a more efficient and controlled way to update snaps across your devices. For implementation details, see {doc}`Manage snaps with validation sets `. - **Use remote scripting to debug** - Use Landscape’s remote scripting interface to collect and parse various logs from your devices to help debug issues @@ -105,11 +107,3 @@ The same issues that affect snap services management on Ubuntu Core also apply t #### Snap configuration The same issues that affect snap configuration on Ubuntu Core also apply to Ubuntu Classic. See the previous section on Ubuntu Core and snap configuration. - -## Known issues with the snap - -### Duplicate machines after reverting snap revisions - -Before revision 329, reverting to a previous snap revision could cause clients to re-register with Landscape, resulting in duplicate machines. This was due to the way the client snap stored data. This issue is now fixed in later revisions (after 329). - -To remove the duplicate machine(s), go to the Landscape portal and remove any machines that are offline and not pinging. In the newer portal, click on the instance, then **Remove from Landscape**. Or in the classic portal, click the computer, then **Remove computer**. You’ll be prompted to confirm in both web portals. diff --git a/docs/how-to-guides/iot-for-devices/install-the-snap.md b/docs/how-to-guides/iot-for-devices/install-the-snap.md index 05f9c8df..5a11cbce 100644 --- a/docs/how-to-guides/iot-for-devices/install-the-snap.md +++ b/docs/how-to-guides/iot-for-devices/install-the-snap.md @@ -12,7 +12,7 @@ This document describes how to install the Landscape Client snap and some genera ## Install and set up the Landscape Client snap ```{note} -You must be running a self-hosted Landscape server version 23.10 or later (or beta) to use the snap. +You must be running a self-hosted Landscape server version 23.10 or later to use the snap. ``` The Landscape Client snap is available in the [Snap Store](https://snapcraft.io/landscape-client). This installation method is suitable for Ubuntu Core devices but can also be used on classic Ubuntu systems. diff --git a/docs/how-to-guides/landscape-installation-and-set-up/juju-ha-installation.md b/docs/how-to-guides/landscape-installation-and-set-up/juju-ha-installation.md index ef80870f..70c004ff 100644 --- a/docs/how-to-guides/landscape-installation-and-set-up/juju-ha-installation.md +++ b/docs/how-to-guides/landscape-installation-and-set-up/juju-ha-installation.md @@ -1,7 +1,7 @@ --- myst: html_meta: - description: "Configure high-availability Landscape deployments with Juju and the landscape-scalable bundle. Deploy HAProxy, RabbitMQ, PostgreSQL, and multiple units." + description: "Configure high-availability Landscape deployments with Juju and the landscape-scalable bundle. Deploy with HAProxy, RabbitMQ, PostgreSQL, and multiple units." --- (how-to-juju-ha-installation)= @@ -9,13 +9,40 @@ myst: > See also: [Juju documentation](https://juju.is/docs/juju) -You can create a scalable, high availability (HA) deployment of Landscape Server by using Juju and the [Landscape Scalable](https://charmhub.io/landscape-scalable) charm bundle. The result is a Juju-managed deployment of Landscape Server and the other services it depends on: HAProxy, RabbitMQ Server, and PostgreSQL. +You can create a scalable, high availability (HA) deployment of Landscape Server by using Juju and the [Landscape Scalable](https://charmhub.io/landscape-scalable) charm bundle. The result is a Juju-managed deployment of Landscape Server and the other services it depends on. -Each of these services will have an independently scalable number of Juju units. Depending on your Juju cloud configuration, these units can be deployed to different availability zones to further improve resilience. +```{important} +This guide covers both the **26.04 beta+ deployment approach** and the older **pre-26.04 deployment approach**. The 26.04 beta version integrates directly with the external HAProxy charm (`2.8/edge`) using the `haproxy-route` interface, replacing the older `reverseproxy` interface. For new deployments, use the 26.04 beta+ approach. For existing deployments, see {ref}`how-to-migrate-to-26-04-charm`. +``` + +## Architecture overview + +### 26.04 beta+ architecture (recommended) + +Starting with the 26.04 beta version, Landscape Server uses the following architecture: + +- **Landscape Server units** for the application +- **HAProxy** charm (`2.8/edge`) for load balancing via the `haproxy-route` interface +- **PostgreSQL 14+** for the database (using the modern `database` interface) +- **RabbitMQ Server** for message queuing +- **Self-signed certificates** charm (or other TLS provider) integrated with HAProxy -This is a simplified model of a full Landscape HA deployment: +HAProxy sits in front of all Landscape Server units and routes traffic to the appropriate service endpoints. -![Landscape scalable charm deployment](https://assets.ubuntu.com/v1/fbb9e2c3-HA_Deployment_Landscape%20(1).png) +```{include} /reuse/charm-ha-architecture-2604.md +``` + +### Before 26.04 + +The older approach uses: + +- **External HAProxy charm** for load balancing +- **PostgreSQL 14** for the database (using the legacy `pgsql` interface) +- **RabbitMQ Server** for message queuing +- **Separate HAProxy units** for traffic management + +```{include} /reuse/charm-ha-architecture-pre-2604.md +``` ## Prerequisites @@ -27,7 +54,300 @@ Before you can deploy the Landscape Scalable charm bundle, you need to: These steps prepare your environment to deploy [machine charms](https://canonical.com/juju/charms-architecture) with Juju and [integrate them using relations](https://documentation.ubuntu.com/juju/3.6/howto/manage-relations/). -## Deploy the charm bundle +```{note} +For improved database performance and scalability in high-load deployments, consider using PgBouncer as a connection pooler between Landscape Server and PostgreSQL. PgBouncer integrates via the Landscape Server charm's `database` relation endpoint, which uses the underlying `postgresql_client` interface. This requires recent Landscape Server charm revisions with `postgresql_client` support. See {ref}`explanation-pgbouncer-integration` for more information. +``` + +## Deployment approach selection + +Choose the appropriate deployment approach based on your needs: + +- **For new deployments:** Use the 26.04 beta+ approach (recommended) +- **For existing deployments:** Continue with the older approach or migrate using {ref}`how-to-migrate-to-26-04-charm` + +--- + +## 26.04 beta+ deployment (recommended) + +This section covers deploying Landscape Server with the external HAProxy charm introduced in version 26.04 beta. + +### Create a Juju model + +```bash +juju add-model landscape-ha +``` + +### Deploy with a custom bundle file + +For the 26.04 beta+ deployment, you'll create a custom bundle file that includes all the necessary components. + +#### Step 1: Create the bundle file + +Create a file named `landscape-ha-26.04.yaml` with the following content: + +```yaml +description: Landscape Scalable +applications: + postgresql: + channel: 16/stable + charm: ch:postgresql + num_units: 3 + options: + plugin_plpython3u_enable: true + plugin_ltree_enable: true + plugin_intarray_enable: true + plugin_debversion_enable: true + plugin_pg_trgm_enable: true + experimental_max_connections: 500 + base: ubuntu@24.04 + + rabbitmq-server: + channel: latest/edge + charm: ch:rabbitmq-server + num_units: 3 + options: + consumer-timeout: 259200000 + + landscape-server: + charm: ch:landscape-server + channel: 26.04/beta + num_units: 3 + options: + landscape_ppa: ppa:landscape/self-hosted-26.04 + min_install: True + root_url: https://landscape.local/ + base: ubuntu@24.04 + + haproxy: + charm: ch:haproxy + channel: 2.8/edge + num_units: 1 + constraints: arch=amd64 + + self-signed-certificates: + charm: ch:self-signed-certificates + channel: 1/stable + num_units: 1 + constraints: arch=amd64 + +relations: + - [landscape-server:inbound-amqp, rabbitmq-server] + - [landscape-server:outbound-amqp, rabbitmq-server] + - [landscape-server:database, postgresql:database] + - [haproxy:certificates, self-signed-certificates:certificates] + - [haproxy:receive-ca-certs, self-signed-certificates:send-ca-cert] + - [landscape-server:appserver-haproxy-route, haproxy:haproxy-route] + - [landscape-server:pingserver-haproxy-route, haproxy:haproxy-route] + - [landscape-server:message-server-haproxy-route, haproxy:haproxy-route] + - [landscape-server:api-haproxy-route, haproxy:haproxy-route] + - [landscape-server:package-upload-haproxy-route, haproxy:haproxy-route] + - [landscape-server:repository-haproxy-route, haproxy:haproxy-route] + - [landscape-server:hostagent-messenger-haproxy-route, haproxy:haproxy-route] + - [landscape-server:ubuntu-installer-attach-haproxy-route, haproxy:haproxy-route] +``` + +```{note} +This bundle uses PostgreSQL 16 and the new `database` interface. Adjust the `root_url` option to match your domain name. +``` + +#### Step 2: Deploy the bundle + +```bash +juju deploy ./landscape-ha-26.04.yaml +``` + +#### Step 3: Monitor the deployment + +Watch the deployment progress: + +```bash +juju status --watch 3s +``` + +Once everything is installed and settled, the `Status` for every application will be `active`: + +```text +Model Controller Cloud/Region Version SLA Timestamp +landscape-ha lxd localhost/lxd 3.5.5 unsupported 10:30:00+00:00 + +App Version Status Scale Charm Channel Rev Base +haproxy active 1 haproxy 2.8/edge 50 ubuntu@24.04 +landscape-server 26.04 active 3 landscape-server 26.04/beta 150 ubuntu@24.04 +postgresql 16.4 active 3 postgresql 16/stable 500 ubuntu@24.04 +rabbitmq-server 3.9.27 active 3 rabbitmq-server latest/edge 200 ubuntu@22.04 +self-signed-certificates active 1 self-signed-certificates 1/stable 12 ubuntu@24.04 +``` + +#### Step 4: Configure license file + +Set your Landscape license: + +```sh +juju config landscape-server "license_file=$(cat your-license-file)" +``` + +#### Step 5: Access Landscape + +Access Landscape via the HAProxy unit IP or your configured `root_url`. Use `juju status` to find the HAProxy unit IP address. + +### Optional: Replace self-signed certificates with a valid certificate + +If you deployed the example bundle above, it includes self-signed certificates (suitable for testing). For production, replace them with a valid certificate, such as one from Let's Encrypt: + +```bash +juju remove-application self-signed-certificates + +juju deploy lego --channel 4/stable +juju config lego server="https://acme-v02.api.letsencrypt.org/directory" +juju config lego email="admin@example.com" +juju config lego plugin="http" + +juju integrate haproxy:certificates lego:certificates +juju integrate haproxy:receive-ca-certs lego:send-ca-cert +``` + +**Prerequisites:** +- Domain in `root_url` must resolve to the HAProxy unit IP +- Port 80 must be accessible for ACME HTTP-01 challenge validation +- Valid email for certificate notifications + +For more details, see the [lego charm documentation](https://charmhub.io/lego/docs/getting-started-with-lego-http01). + +(heading-lbaas-installation)= +### Optional: External Load Balancer with Cross-Model Integration (LBaaS) + +For production deployments requiring an external load balancer in a separate infrastructure layer, you can deploy HAProxy in a **separate Juju model** and connect it to Landscape Server using cross-model relations (also known as LBaaS - Load Balancer as a Service). + +This approach is useful when: +- You want to manage your load balancer infrastructure separately from application deployments +- You need a dedicated load balancer shared across multiple applications +- You want to isolate load balancer lifecycle from application lifecycle + +#### Step 1: Create a separate model for the load balancer + +```bash +juju add-model lbaas +juju switch lbaas +``` + +#### Step 2: Deploy HAProxy and TLS certificates in the LBaaS model + +Deploy HAProxy: + +```sh +juju deploy haproxy --channel 2.8/edge +juju expose haproxy +``` + +Deploy the TLS certificates provider: + +```sh +juju deploy lego --channel 4/stable +juju config lego server="https://acme-v02.api.letsencrypt.org/directory" +juju config lego email="admin@example.com" +juju config lego plugin="http" +``` + +Wait for both applications to become active: + +```sh +juju wait-for application haproxy --query='status=="active"' +juju wait-for application lego --query='status=="active"' +``` + +Integrate HAProxy with the TLS certificates provider: + +```sh +juju integrate haproxy:certificates lego:certificates +juju integrate haproxy:receive-ca-certs lego:send-ca-cert +``` + +#### Step 3: Create a cross-model offer + +```sh +juju offer haproxy:haproxy-route +``` + +This creates an offer that can be consumed from other Juju models. + +#### Step 4: Consume the HAProxy offer and integrate Landscape Server + +Switch back to your Landscape Server model: + +```sh +juju switch landscape-ha +``` + +Consume the HAProxy offer from the lbaas model: + +```sh +juju consume admin/lbaas.haproxy lbaas-haproxy +``` + +Integrate Landscape Server's route endpoints directly with the external HAProxy: + +```sh +juju integrate landscape-server:appserver-haproxy-route lbaas-haproxy:haproxy-route +juju integrate landscape-server:pingserver-haproxy-route lbaas-haproxy:haproxy-route +juju integrate landscape-server:message-server-haproxy-route lbaas-haproxy:haproxy-route +juju integrate landscape-server:api-haproxy-route lbaas-haproxy:haproxy-route +juju integrate landscape-server:package-upload-haproxy-route lbaas-haproxy:haproxy-route +juju integrate landscape-server:repository-haproxy-route lbaas-haproxy:haproxy-route +juju integrate landscape-server:hostagent-messenger-haproxy-route lbaas-haproxy:haproxy-route +juju integrate landscape-server:ubuntu-installer-attach-haproxy-route lbaas-haproxy:haproxy-route +``` + +Wait for the deployment to complete: + +```sh +juju wait-for application landscape-server --query='status=="active"' +``` + +#### Step 5: Configure DNS and access + +Get the HAProxy public IP address: + +```sh +juju switch lbaas +juju status haproxy --format=json | jq -r '.applications.haproxy.units | to_entries[0].value["public-address"]' +``` + +Configure your DNS to point your hostname (matching `root_url`) to this IP address. + +Access Landscape via: `https://landscape.example.com/` + +```{mermaid} +flowchart TD + Client([Client]) + subgraph lbaas[Juju model: lbaas] + HAProxy["HAProxy
2.8/edge"] + TLS[lego / TLS provider] + end + subgraph landscape-ha[Juju model: landscape-ha] + LS0[landscape-server/0] + LS1[landscape-server/1] + LS2[landscape-server/2] + PG[(PostgreSQL)] + RMQ[RabbitMQ Server] + end + TLS -- certificates --> HAProxy + Client -- HTTPS --> HAProxy + HAProxy -- "haproxy-route (cross-model)" --> LS0 + HAProxy -- "haproxy-route (cross-model)" --> LS1 + HAProxy -- "haproxy-route (cross-model)" --> LS2 + LS0 & LS1 & LS2 --- PG + LS0 & LS1 & LS2 --- RMQ +``` + +## Pre-26.04 deployment + +```{warning} +This deployment approach is **deprecated**. For new deployments, use the 26.04 beta+ approach described above. For existing deployments, consider migrating using {ref}`how-to-migrate-to-26-04-charm`. +``` + +This section covers the older deployment approach using the external HAProxy charm. This approach is maintained for existing deployments only. + +### Deploy the charm bundle You can deploy the Landscape Scalable charm bundle using one of two main methods. The methods are: @@ -366,25 +686,41 @@ Machine State Address Inst id Base AZ Message You now have Landscape Server set up for a high-availability deployment. Next, you need to set up your clients by {ref}`installing the Landscape Client charm ` on each client, and configuring them with [the `juju config` command](https://documentation.ubuntu.com/juju/3.6/reference/juju-cli/list-of-juju-cli-commands/config/). You may also need to change your SSL certificate configuration. See the {ref}`how-to-header-configure-haproxy-with-ssl-cert` section in this guide for more information. -## Create a SSL certificate with LetsEncrypt (Optional) +(how-to-header-configure-haproxy-with-ssl-cert)= +## Configure SSL certificates (pre-26.04 deployments only) + +```{warning} +This section applies **only to pre-26.04 deployments** using the external HAProxy charm. For 26.04 beta+ deployments, see the TLS certificates configuration in the 26.04 deployment section above. +``` + +### For pre-26.04 deployments with external HAProxy charm + +The older HAProxy charm uses self-signed SSL certificates by default. Landscape Clients and your browser won't trust this certificate. + +**Option 1: Manual certificate configuration** -If your Landscape instance has a public IP, and your FQDN resolves to that public IP, run the following code to get a valid SSL certificate from LetsEncrypt. Replace `` with an email address where certificate renewal reminders can be sent. +If you have a valid SSL certificate: ```bash -sudo certbot certonly --standalone -d $FQDN --non-interactive --agree-tos --email +juju config haproxy ssl_cert="$(base64 fullchain.pem)" ssl_key="$(base64 privkey.pem)" ``` -This will produce a `fullchain.pem` and `privkey.pem` file which you need for HAProxy SSL termination. +**Option 2: Let's Encrypt with certbot** -(how-to-header-configure-haproxy-with-ssl-cert)= -## Configure HAProxy with an SSL certificate +If your Landscape instance has a public IP and your FQDN resolves to it: -If you followed the previous instructions in this guide, you'll have a Landscape Server high availability deployment. The HAProxy application in this deployment uses a self-signed SSL certificate for secure HTTPS. Landscape Clients and your browser won't trust this certificate by default. +```bash +# On a machine with port 80 accessible +sudo certbot certonly --standalone -d $FQDN --non-interactive --agree-tos --email admin@example.com +``` -If you have a valid SSL certificate, you can configure HAProxy to use it by executing the following `juju config` commands. `fullchain.pem` is the public, full-chain SSL certificate and `privkey.pem` is the certificate's private key. +This produces `fullchain.pem` and `privkey.pem` files: ```bash -juju config haproxy ssl_cert="$(base64 fullchain.pem)" ssl_key="$(base64 privkey.pem)" +juju config haproxy ssl_cert="$(base64 /etc/letsencrypt/live/$FQDN/fullchain.pem)" \ + ssl_key="$(base64 /etc/letsencrypt/live/$FQDN/privkey.pem)" ``` -Once your SSL certificate is in place, your Landscape Clients and browser should trust HTTPS connections to your Landscape Server deployment, so long as your certificate remains valid. +```{note} +Certificate renewal must be handled manually for pre-26.04 deployments. Consider migrating to 26.04 beta+ for automatic certificate management via the `tls-certificates` interface. +``` diff --git a/docs/how-to-guides/landscape-installation-and-set-up/juju-installation.md b/docs/how-to-guides/landscape-installation-and-set-up/juju-installation.md index d07f0d68..82fe61e8 100644 --- a/docs/how-to-guides/landscape-installation-and-set-up/juju-installation.md +++ b/docs/how-to-guides/landscape-installation-and-set-up/juju-installation.md @@ -29,26 +29,88 @@ If you have an Ubuntu Pro subscription, attach your Pro token to each machine th When deploying with Juju, you will use a Juju bundle. A bundle is an encapsulation of all of the parts needed to deploy the required services as well as associated relations and configurations that the deployment requires. +```{important} +Starting with the **26.04 beta version** of the Landscape Server charm, the deployment architecture has changed significantly. The charm now integrates directly with the external HAProxy charm (`2.8/edge`) using the `haproxy-route` interface and no longer uses the legacy `reverseproxy` interface. If you have an existing deployment using the older approach, see {ref}`how-to-migrate-to-26-04-charm` for migration instructions. +``` + +### Deployment approaches + +There are two deployment approaches depending on which version of the Landscape Server charm you're using: + +#### Pre-26.04 deployment + +The older deployment uses: +- External HAProxy charm for load balancing +- PostgreSQL 14 with the legacy `pgsql` interface +- Separate HAProxy unit(s) for traffic management + +This approach is deprecated and should only be used for existing deployments that haven't migrated yet. + +#### 26.04 beta+ deployment (recommended) + +The new deployment approach uses: +- **External HAProxy charm** (`2.8/edge`) for load balancing via the `haproxy-route` interface +- PostgreSQL 16 with the modern `database` interface +- TLS certificates provided via the `tls-certificates` interface integrated with HAProxy (e.g., `self-signed-certificates` charm) + +Key benefits of the new approach: +- HAProxy charm handles all traffic routing and TLS termination +- True high-availability with multiple Landscape Server units behind HAProxy +- Better scalability and resilience + +For detailed instructions on deploying with the new architecture, see {ref}`how-to-juju-ha-installation`. + ### landscape-scalable bundle > See also: [Landscape-scalable bundle on Charmhub](https://charmhub.io/landscape-scalable) -In the **landscape-scalable** bundle configuration, each service gets its own machine. Currently that means you will need 4 machines for Landscape, and one for the controller node. Test it out using: +The **landscape-scalable** bundle provides a reference configuration for deploying Landscape Server in a high-availability setup. The bundle configuration varies depending on the charm version: + +**For 26.04 beta+ deployments:** ```bash -juju deploy landscape-scalable +juju deploy landscape-scalable --channel 26.04/beta ``` -For more detailed instructions on deploying the Landscape server with the bundle, see {ref}`how-to-juju-ha-installation`. +This will deploy: +- Multiple Landscape Server units +- HAProxy (`2.8/edge`) for load balancing +- PostgreSQL 16 for the database +- RabbitMQ Server for message queuing +- Self-signed certificates for TLS (integrated with HAProxy) + +**For older deployments:** + +```bash +juju deploy landscape-scalable --channel latest/stable +``` + +This deploys the older architecture with the external HAProxy charm. ### Other bundles -The Landscape Scalable bundle is the only bundle currently supported. Previously, there were two additional bundles: `landscape-dense` and `landscape-dense-maas`. These bundles are now deprecated. +Previously, there were additional bundles: `landscape-dense` and `landscape-dense-maas`. These bundles are now deprecated and should not be used for new deployments. ## Access self-hosted Landscape -Once the deployment has finished, get the address of the first `haproxy` unit and access it with your browser: +Once the deployment has finished, Landscape Server is accessible in different ways depending on the deployment approach: -```bash -juju status haproxy +**Pre-26.04 deployment:** + + - Access via the IP address of the first `haproxy` unit + - HAProxy typically runs on port 443 (HTTPS) + +**26.04 beta+ deployment:** + + - Access via the HAProxy unit IP address or your configured `root_url` + - HAProxy handles load balancing across all Landscape Server units + +**With external load balancer (LBaaS):** + + - When using a cross-model HAProxy deployment + - Access via the hostname specified in your `root_url` + - The external HAProxy distributes traffic across Landscape Server units + +```{tip} +For the 26.04 beta+ deployment, it's recommended to set the `root_url` option and configure DNS to point to your HAProxy unit IP address, or to an external load balancer if you're using LBaaS. ``` diff --git a/docs/how-to-guides/security/harden-your-deployment.md b/docs/how-to-guides/security/harden-your-deployment.md index 8a5a7bbb..8e4687cc 100644 --- a/docs/how-to-guides/security/harden-your-deployment.md +++ b/docs/how-to-guides/security/harden-your-deployment.md @@ -5,6 +5,7 @@ myst: --- (how-to-harden-deployment)= + # How to harden your Landscape deployment You have many options when hardening your Landscape deployment. @@ -22,15 +23,15 @@ Port 80 is only needed for Landscape's repository mirroring features. If you don The other applications in your deployment only require enough network access to communicate with each other. Using the default configuration, applications listen on these ports for incoming traffic: -- Landscape server: 6554, and 8080-9100, inclusive -- PostgreSQL: 5432 -- RabbitMQ server: 5672 +* Landscape server: 6554, and 8080-9100, inclusive +* PostgreSQL: 5432 +* RabbitMQ server: 5672 for TCP or 5671 for TLS Make sure these ports are exposed for internal traffic between the applications. **None of these ports should be exposed to external traffic.** ## Secure external traffic -For more security, you should configure HAProxy or Apache with a TLS certificate. LetsEncrypt provides an easy way to create a certificate, and you can use LetsEncrypt with HAProxy by following the directions in the [Juju HA installation guide for Landscape](/how-to-guides/landscape-installation-and-set-up/juju-ha-installation.md/#configure-haproxy-with-an-ssl-certificate). +For more security, you should configure HAProxy or Apache with a TLS certificate. LetsEncrypt provides an easy way to create a certificate, and you can use LetsEncrypt with HAProxy by following the directions in the {ref}`Juju HA installation guide for Landscape `. You can use LetsEncrypt with Apache by following the same directions to acquire the certificate, then install it by following the [configure web server](/how-to-guides/landscape-installation-and-set-up/manual-installation.md#configure-web-server) section of the manual installation guide. @@ -74,71 +75,85 @@ Ubuntu LTS releases with Ubuntu Pro can take advantage of the [Ubuntu Security G If you used Juju to deploy Landscape, you can follow [Juju's hardening guide](https://documentation.ubuntu.com/juju/3.6/howto/manage-your-juju-deployment/harden-your-juju-deployment/#harden-your-deployment) to harden the Juju aspects of your deployment. -## mTLS in Landscape +## TLS and mTLS in Landscape The transport-layer security (TLS) protocol secures communication by requiring the server to present a certificate and private key. With mutual TLS (mTLS), clients must also present a certificate issued by the same certificate authority (CA), so both sides authenticate each other. -Landscape can be configured to use mTLS for its internal services, and for connections to external services like RabbitMQ and HashiCorp Vault. - - - -```{note} -Standard TLS connections that do not enforce mTLS are not supported. -``` +Landscape can be configured to use basic TLS or mTLS for its internal services, and for connections to external services like RabbitMQ and HashiCorp Vault. ### CA Certificate -You will need the CA certificate used to sign your certificates. Ensure it has the following permissions: +The CA certificate is used by servers and clients in Landscape to identify who the certificates were issued by. mTLS insists that both the client and the server present valid TLS credentials issued by the same CA. However, if a TLS server is using self-signed credentials, a client may connect to it by saving the CA certificate in its system CA bundle, or by presenting it when it attempts to connect. + +To set up TLS or mTLS, you will need the CA certificate used to sign your certificates. Ensure it has the following permissions: ```sh -sudo chmod 644 /path/to/ca/ca-cert.pem +sudo chmod 644 /path/to/ca/ca-cert.pem sudo chown root:root /path/to/ca/ca-cert.pem ``` ### RabbitMQ -Obtain TLS server credentials and the CA certificate for the RabbitMQ server and provide their paths in `/etc/rabbitmq/rabbitmq.conf`, along with other required fields: +To enable TLS, obtain TLS credentials for the RabbitMQ server and provide their paths in `/etc/rabbitmq/rabbitmq.conf`, along with other required fields: ```ini -listeners.tcp = none -listeners.ssl.default = 5672 +listeners.ssl.default = 5671 ssl_options.certfile = /path/to/rabbitmq/server-cert.pem ssl_options.keyfile = /path/to/rabbitmq/server-key.pem +ssl_options.verify = verify_none +ssl_options.fail_if_no_peer_cert = false +``` + +Make sure the TLS credential files are owned by the `rabbitmq` user: + +```sh +sudo chown rabbitmq:rabbitmq /path/to/rabbitmq/server-cert.pem /path/to/rabbitmq/server-key.pem +sudo chmod 600 /path/to/rabbitmq/server-key.pem +sudo chmod 644 /path/to/rabbitmq/server-cert.pem +``` + +To have the RabbitMQ server enforce mTLS, add the following fields to the config: + +```ini ssl_options.cacertfile = /path/to/ca/ca-cert.pem -ssl_options.verify = verify_peer -ssl_options.fail_if_no_peer_cert = true auth_mechanisms.1 = EXTERNAL ssl_cert_login_from = common_name ``` -Additionally, edit `/etc/rabbitmq/enabled_plugins`: +Then, add the following options: `ssl_options.verify` and `ssl_options.fail_if_no_peer_cert`: ```ini -[rabbitmq_auth_mechanism_ssl]. +ssl_options.verify = verify_peer +ssl_options.fail_if_no_peer_cert = true ``` -Set ownership and permissions: +Edit `/etc/rabbitmq/enabled_plugins`: -```sh -sudo chown rabbitmq:rabbitmq /path/to/rabbitmq/server-cert.pem /path/to/rabbitmq/server-key.pem -sudo chmod 600 /path/to/rabbitmq/server-key.pem -sudo chmod 644 /path/to/rabbitmq/server-cert.pem +```ini +[rabbitmq_auth_mechanism_ssl]. ``` -Restart RabbitMQ: +Finally, restart RabbitMQ: ```sh sudo systemctl restart rabbitmq-server ``` -Landscape connects to RabbitMQ via the credentials defined in the `[broker]` section of your `service.conf` file. -Since RabbitMQ is listening using mTLS, delete the `password` field from the section if present and provide the paths to TLS credentials to enable TLS certificate-based authentication: +Landscape connects to RabbitMQ via the credentials defined in the `[broker]` section of your `service.conf` file, and it can connect via TLS or mTLS. + +If RabbitMQ is listening using TLS, add the following field to the `[broker]` section: + +```ini +[broker] +ssl_client_ca_cert = /path/to/ca/ca-cert.pem +``` + +If RabbitMQ is enforcing mTLS, delete the `password` field from the section if present and provide the paths to a TLS credential pair to enable certificate-based authentication: ```ini [broker] ssl_client_cert = /path/to/broker/client-cert.pem ssl_client_private_key = /path/to/broker/client-key.pem -ssl_client_ca_cert = /path/to/ca/ca-cert.pem ``` Ensure the broker credentials are owned by the `landscape` user: @@ -157,24 +172,23 @@ sudo lsctl restart ### Landscape services -The following Landscape services can be configured to use mTLS: +The following Landscape services can be configured to use TLS or mTLS: - `landscape-async-frontend` - `landscape-secrets-service` Each service can have its own server certificate and can be configured to require clients to authenticate via their own TLS credentials. -The `secrets-service` can additionally be configured to connect to HashiCorp Vault as a client via mTLS. +The `secrets-service` can additionally be configured to connect to HashiCorp Vault as a client via TLS or mTLS. #### Async Frontend -The `async-frontend` service can listen using mTLS for incoming connections. +The `async-frontend` service can listen using TLS or mTLS for incoming connections. -Obtain TLS server credentials, and add the paths in the `[async_frontend]` section in `service.conf`: +Obtain a TLS server certificate and private key pair, and add the paths in the `[async_frontend]` section in `service.conf`: ```ini ssl_server_cert = /path/to/async_frontend/server-cert.pem ssl_server_private_key = /path/to/async_frontend/server-key.pem -ssl_server_ca_cert = /path/to/ca/ca-cert.pem ``` Set ownership and permissions: @@ -185,6 +199,12 @@ sudo chmod 600 /path/to/async_frontend/server-key.pem sudo chmod 644 /path/to/async_frontend/server-cert.pem ``` +To further enable mTLS, you must also provide the path to the CA cert: + +```ini +ssl_server_ca_cert = /path/to/ca/ca-cert.pem +``` + Restart Landscape: ```sh @@ -193,7 +213,7 @@ sudo lsctl restart #### Secrets Service (with HashiCorp Vault) -The `secrets-service` can listen using mTLS for incoming connections, and it can connect to a Vault server using mTLS. See HashiCorp's guide on [hardening your Vault server](https://developer.hashicorp.com/vault/docs/concepts/production-hardening). +The `secrets-service` can listen using TLS or mTLS for incoming connections, and it can connect to a Vault server using TLS or mTLS. See HashiCorp's guide on [hardening your Vault server](https://developer.hashicorp.com/vault/docs/concepts/production-hardening). Update the `vault_url` field in the `[secrets]` section of your `service.conf`, and make sure both URLs are using HTTPS: @@ -203,19 +223,29 @@ service_url = https://localhost:26155 vault_url = https://localhost:8200 ``` -Since Vault is enforcing mTLS, you must also obtain or generate client TLS credentials issued by the same CA and append them to the section: +If the Vault server is listening with TLS, you must also provide the `secrets-service` with paths to the CA certificate used by Vault: + +```ini +ssl_client_ca_cert = /path/to/vault/vault-ca.pem +``` + +To make the service itself listen using TLS, append the paths to a TLS credential pair: + +```ini +ssl_server_private_key = /path/to/secrets/server-key.pem +ssl_server_cert = /path/to/secrets/server-cert.pem +``` + +To connect to a Vault server that is enforcing mTLS, obtain or generate client TLS credentials issued by the same CA and append them to the section: ```ini ssl_client_private_key = /path/to/client/client-key.pem ssl_client_cert = /path/to/client/client-cert.pem -ssl_client_ca_cert = /path/to/ca/ca-cert.pem ``` -To make the Secrets Service listen using mTLS, provide the paths to the certificate and the private key in the `[secrets]` section of your `service.conf`: +To make the Secrets Service listen using mTLS, you must also include the path to the CA cert: ```ini -ssl_server_private_key = /path/to/secrets/server-key.pem -ssl_server_cert = /path/to/secrets/server-cert.pem ssl_server_ca_cert = /path/to/ca/ca-cert.pem ``` diff --git a/docs/how-to-guides/upgrade/index.md b/docs/how-to-guides/upgrade/index.md index 7a59868a..7928aab3 100644 --- a/docs/how-to-guides/upgrade/index.md +++ b/docs/how-to-guides/upgrade/index.md @@ -15,5 +15,4 @@ Upgrade your Landscape Server deployment to newer versions. These guides cover s Upgrade Landscape Upgrade to Landscape 24.04 LTS -``` - +Upgrade to Landscape 26.04 LTS (charm) diff --git a/docs/how-to-guides/upgrade/migrate-to-26-04-charm.md b/docs/how-to-guides/upgrade/migrate-to-26-04-charm.md new file mode 100644 index 00000000..42c78edb --- /dev/null +++ b/docs/how-to-guides/upgrade/migrate-to-26-04-charm.md @@ -0,0 +1,191 @@ +--- +myst: + html_meta: + description: "Migrate to Landscape 26.04 LTS (charm)." +--- + +(how-to-migrate-to-26-04-charm)= +# How to migrate to Landscape 26.04 LTS (charm) + +```{note} +The Landscape Server charm for 26.04 is currently in beta. +``` + +This guide explains how to migrate from an older Landscape Server charm deployment (pre-26.04) to the 26.04 LTS beta+ version with an external HAProxy charm using the `haproxy-route` interface. + +## Architectural changes + +The 26.04 beta version introduces significant architectural changes: + +| Aspect | Landscape 26.04 LTS beta+ | Pre-26.04 | +| ------------------------ | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | +| **Load balancing** | External HAProxy charm (`haproxy` at `2.8/edge`, `haproxy-route` interface) | External HAProxy charm (`reverseproxy` interface) | +| **PostgreSQL interface** | Modern `database` interface (PostgreSQL 14+) | Legacy `pgsql` interface (PostgreSQL 14) | +| **PostgreSQL relation** | `landscape-server:database` → `postgresql:database` | `landscape-server:db` → `postgresql:db-admin` | +| **RabbitMQ relation** | `landscape-server:inbound-amqp` and `landscape-server:outbound-amqp` → `rabbitmq-server` (25.10+) | `landscape-server:amqp` → `rabbitmq-server:amqp` (pre-25.10) | +| **HAProxy relation** | `landscape-server:*-haproxy-route` → `haproxy:haproxy-route` (8 route endpoints) | `landscape-server:website` → `haproxy:reverseproxy` | +| **TLS certificates** | `haproxy:certificates` → TLS provider (e.g., `self-signed-certificates`, `lego`) | HAProxy self-signed or manual config | +| **Access method** | HAProxy unit IP or `root_url` | HAProxy unit IP | +| **HA capabilities** | HAProxy units for load balancing | HAProxy units for load balancing | + +## Migration steps + +### Step 1: Backup your database + +Before making any changes, back up your Landscape database following the backup procedures in {ref}`how-to-back-up-restore-tear-down-charmed-deployment`. + +### Step 2: Remove incompatible relations + +Remove the older HAProxy relation: + +```bash +juju remove-relation landscape-server:website haproxy:reverseproxy --force +``` + +**For deployments older than 25.10 only:** + +Remove the older RabbitMQ relation: + +```bash +juju remove-relation landscape-server:amqp rabbitmq-server:amqp --force +``` + +```{note} +If you're migrating from 25.10 or later, you already have the `inbound-amqp` and `outbound-amqp` relations. +``` + +### Step 3: Deploy HAProxy and TLS certificates provider + +Deploy the HAProxy charm and a TLS certificates provider before refreshing the charm. This gives them time to become active while other operations proceed. + +First, deploy the HAProxy charm: + +```bash +juju deploy haproxy --channel 2.8/edge +``` + +**For testing/development with self-signed certificates:** + +```bash +juju deploy self-signed-certificates +juju integrate haproxy:certificates self-signed-certificates:certificates +juju integrate haproxy:receive-ca-certs self-signed-certificates:send-ca-cert +``` + +**For production with Let's Encrypt:** + +```bash +juju deploy lego --channel 4/stable +juju config lego server="https://acme-v02.api.letsencrypt.org/directory" +juju config lego email="admin@example.com" +juju config lego plugin="http" +juju integrate haproxy:certificates lego:certificates +juju integrate haproxy:receive-ca-certs lego:send-ca-cert +``` + +**Prerequisites for Let's Encrypt HTTP-01 challenge:** +- Domain in `root_url` must resolve to the HAProxy unit IP +- Port 80 must be accessible for ACME HTTP-01 challenge validation +- Valid email address for certificate notifications + +See the [lego charm documentation](https://charmhub.io/lego) for DNS-01 challenge configuration. + +**For custom CA certificates:** + +```bash +juju deploy manual-tls-certificates --channel stable +juju config manual-tls-certificates ca-certificate="$(base64 -w0 ca.crt)" +juju config manual-tls-certificates certificate="$(base64 -w0 server.crt)" +juju config manual-tls-certificates private-key="$(base64 -w0 server.key)" +juju integrate haproxy:certificates manual-tls-certificates:certificates +``` + +See the [manual-tls-certificates charm documentation](https://charmhub.io/manual-tls-certificates) for details. + +```{note} +The `manual-tls-certificates` charm provides both the server certificate and any associated CA material over the `certificates` relation, so an additional `haproxy:receive-ca-certs` integration is not required for this provider. +``` + +### Step 4: Refresh the charm + +Refresh the Landscape Server charm to the 26.04 beta version: + +```bash +juju refresh landscape-server --channel 26.04/beta +``` + +Wait for the refresh to complete and the services to restart: + +```bash +juju status --watch 2s +``` + +### Step 5: Integrate Landscape Server with HAProxy + +Add the HAProxy route integrations for all Landscape Server services: + +```bash +juju integrate landscape-server:appserver-haproxy-route haproxy:haproxy-route +juju integrate landscape-server:pingserver-haproxy-route haproxy:haproxy-route +juju integrate landscape-server:message-server-haproxy-route haproxy:haproxy-route +juju integrate landscape-server:api-haproxy-route haproxy:haproxy-route +juju integrate landscape-server:package-upload-haproxy-route haproxy:haproxy-route +juju integrate landscape-server:repository-haproxy-route haproxy:haproxy-route +juju integrate landscape-server:hostagent-messenger-haproxy-route haproxy:haproxy-route +juju integrate landscape-server:ubuntu-installer-attach-haproxy-route haproxy:haproxy-route +``` + +```{important} +The `ssl_cert` and `ssl_key` charm configuration have been removed and are no longer supported in the 26.04 beta charm. TLS is now managed by the HAProxy charm via the `tls-certificates` interface. +``` + +### Step 6: Add new RabbitMQ relations (pre-25.10 deployments only) + +```{note} +This step can be skipped on deployments on 25.10 or newer, as they will already have these relations. +``` + +For deployments older than 25.10, add the new separate inbound and outbound AMQP relations: + +```bash +juju integrate landscape-server:inbound-amqp rabbitmq-server +juju integrate landscape-server:outbound-amqp rabbitmq-server +``` + +### Step 7: Update PostgreSQL (optional) + +If you want to upgrade to a newer PostgreSQL version (e.g., from 14 to 16) as part of this migration, follow the backup and restore procedures in {ref}`how-to-back-up-restore-tear-down-charmed-deployment` to migrate your data to a new PostgreSQL deployment. + +```{note} +PostgreSQL upgrade is optional. The 26.04 beta charm uses the modern `database` interface which works with PostgreSQL 14 and above. + +The legacy `db` endpoint (legacy `pgsql` interface) is still supported for backwards compatibility but only works with PostgreSQL 14. It is recommended to migrate to the modern `database` interface since Charmed PostgreSQL 16+ does not support the legacy interface. +``` + +### Step 8: Verify the deployment + +Check that all services are active: + +```bash +juju status +``` + +Access Landscape via the HAProxy unit IP or your configured `root_url`. Use `juju status` to find the HAProxy unit IP address. + +```{tip} +For testing access by hostname before DNS is configured, add the HAProxy unit IP (or your external HAProxy IP if using LBaaS) to `/etc/hosts` on your local machine with the hostname from your `root_url`. For example: `10.1.77.133 landscape.example.com` +``` + +Log in and verify: +- All computers are visible +- Activities and alerts are present +- User accounts and permissions are intact + +For more information about `juju refresh`, see the [Juju documentation on charm upgrades](https://documentation.ubuntu.com/juju/3.6/howto/manage-charms/#update-a-charm). + +## Additional resources + +- {ref}`how-to-juju-ha-installation` - Full HA deployment guide +- {ref}`explanation-charm-compatibility` - Charm compatibility details +- [Landscape Server charm documentation](https://charmhub.io/landscape-server) +- [PostgreSQL charm documentation](https://charmhub.io/postgresql) diff --git a/docs/how-to-guides/web-portal/web-portal-24-04-or-later/manage-instances.md b/docs/how-to-guides/web-portal/web-portal-24-04-or-later/manage-instances.md index 1008818c..e0d5cfca 100644 --- a/docs/how-to-guides/web-portal/web-portal-24-04-or-later/manage-instances.md +++ b/docs/how-to-guides/web-portal/web-portal-24-04-or-later/manage-instances.md @@ -1,7 +1,7 @@ --- myst: html_meta: - description: "View, search, and manage instances in Landscape's 24.04+ portal. Learn to manage instances, view details, and perform bulk operations." + description: "View, search, and manage instances in Landscape's 24.04+ portal. Learn to manage instances, use saved searches, view details, and perform bulk operations." --- (how-to-web-portal-manage-instances)= @@ -20,12 +20,44 @@ Go to **Instances** from the sidebar to view your instances. The default view di You can also use the search bar to find specific client instances. -To view detailed information about a specific instance, click on the instance name to open its **Info** page. That page displays information such as: +## Save and reuse instance searches -- Status -- Last ping time -- Access group -- Registration details +Saved searches let you save and reuse instance queries in Landscape. They’re useful for quickly filtering instances by specific criteria without rebuilding the query each time. + +Note that saved searches can only be used to filter instances. + +```{note} +This feature is only available in self-hosted **Landscape 26.04 LTS** and later. +``` + +**Create a saved search** + +There are two ways to create a saved search: directly from the search bar or from the full saved searches management panel. The first option lets you quickly save a query you're currently using, and the second option lets you create a new search query. + +**(Option #1) From the search bar:** + +1. Type your query in the **Instances** search bar +2. Click **Save search** +3. Enter a title > **Add saved search** + +**(Option #2) From the management panel:** + +1. Click the search bar > **Manage** > **Add saved search** +2. Enter a title and search query > **Add saved search** + +The search query editor provides auto-complete and syntax suggestions to help you construct valid queries. + +**Apply a saved search** + +To filter your instances, click the search bar on the **Instances** page and select a saved search from the list. + +**Edit or delete a saved search** + +You can manage your saved searches from the dropdown list or the management panel. + +To **edit**, click the **pencil** icon next to the saved search. Note that you can only edit the search query, not the title. + +To **delete**, click the **trash can** icon next to the saved search and confirm the deletion. ## Manage individual instances @@ -39,6 +71,13 @@ From the specific instance's page, click into any of the tabs to view informatio - Fix security issues - View hardware information +To view detailed information about a specific instance, click on the instance name to open its **Info** page. That page displays information such as: + +- Status +- Last ping time +- Access group +- Registration details + ## Perform actions on multiple instances You can perform certain actions on multiple instances at once, such as restarting, attaching Pro tokens, or running scripts on the selected instances. From the **Instances** page: diff --git a/docs/redirects.txt b/docs/redirects.txt index 58174caa..7c8eafb2 100644 --- a/docs/redirects.txt +++ b/docs/redirects.txt @@ -53,3 +53,5 @@ reference/terms/repositories/ explanation/features/repository-mirroring/ reference/terms/scripts/ explanation/features/remote-script-execution/ reference/logs/logs/ reference/logs/ + +reference/release-notes/what-s-new-in-beta/ reference/release-notes/ diff --git a/docs/reference/api/legacy-api-endpoints/role-based-access-control.md b/docs/reference/api/legacy-api-endpoints/role-based-access-control.md index ce99ea18..f92a81ba 100644 --- a/docs/reference/api/legacy-api-endpoints/role-based-access-control.md +++ b/docs/reference/api/legacy-api-endpoints/role-based-access-control.md @@ -160,7 +160,7 @@ Create a new access group. Arguments: -- `title`: The title of the access group. +- `title`: The title of the access group. The only special characters allowed are `-` and `.`. Disallowed special characters will be stripped from the title. - `parent`: Optionally, the name of the access group that this access group should be added as a child of. If this parameter is omitted the child will be added below the root access group of the account. The following errors may be raised: diff --git a/docs/reference/api/rest-api-endpoints/accounts.md b/docs/reference/api/rest-api-endpoints/accounts.md index 0794f2a3..decf9f49 100644 --- a/docs/reference/api/rest-api-endpoints/accounts.md +++ b/docs/reference/api/rest-api-endpoints/accounts.md @@ -27,7 +27,10 @@ Required parameters: Example request: ```bash -curl -X POST https://landscape.canonical.com/api/v2/accounts -H "Authorization: Bearer $JWT" -d '{"title": "Onward, Inc."}' +curl -X POST https://landscape.canonical.com/api/v2/accounts \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"title": "Onward, Inc."}' ``` Example response: @@ -96,7 +99,10 @@ Required parameters: Example request: ```bash -curl -X POST https://landscape.example.com/api/v2/standalone-account -H "Authorization: Bearer $JWT" -d '{"email": "john@example.com", "name": "John Doe", "password": "Passw0rd"}' +curl -X POST https://landscape.example.com/api/v2/standalone-account \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"email": "john@example.com", "name": "John Doe", "password": "Passw0rd"}' ``` Example response: diff --git a/docs/reference/api/rest-api-endpoints/administrators.md b/docs/reference/api/rest-api-endpoints/administrators.md index 619f6941..2f317154 100644 --- a/docs/reference/api/rest-api-endpoints/administrators.md +++ b/docs/reference/api/rest-api-endpoints/administrators.md @@ -28,7 +28,10 @@ Optional parameters: Example request: ```bash -curl -X PUT https://landscape.canonical.com/api/v2/administrators/6 -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" -d '{"roles": ["ServerAdmin", "DesktopAdmin"]}' +curl -X PUT https://landscape.canonical.com/api/v2/administrators/6 \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"roles": ["ServerAdmin", "DesktopAdmin"]}' ``` Example response: diff --git a/docs/reference/api/rest-api-endpoints/child-instance-profiles.md b/docs/reference/api/rest-api-endpoints/child-instance-profiles.md index 3b335627..277b83ec 100644 --- a/docs/reference/api/rest-api-endpoints/child-instance-profiles.md +++ b/docs/reference/api/rest-api-endpoints/child-instance-profiles.md @@ -128,11 +128,20 @@ Optional parameters: Example requests: ```bash -curl -X POST https://landscape.canonical.com/api/v2/child-instance-profiles -H "Authorization: Bearer $JWT" -d '{"title": "Stock Ubuntu 24.04", "description": "The image from the store", "image_name": "Ubuntu-24.04", "tags": ["windows_laptops", "windows_desktops"]}' - -curl -X POST https://landscape.canonical.com/api/v2/child-instance-profiles -H "Authorization: Bearer $JWT" -d "{\"title\": \"Customized Ubuntu 24.04\", \"description\": \"The image from the store customized\", \"image_name\": \"Ubuntu-24.04\", \"cloud_init\": \"$(base64 --wrap=0 < cloud_init.yaml)\"}" - -curl -X POST https://landscape.canonical.com/api/v2/child-instance-profiles -H "Authorization: Bearer $JWT" -d '{"title": "Custom Rootfs Image", "description": "My custom image", "image_name": "CustomUbuntu", "image_source": "https://example.com/myimage.tar.gz"}' +curl -X POST https://landscape.canonical.com/api/v2/child-instance-profiles \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"title": "Stock Ubuntu 24.04", "description": "The image from the store", "image_name": "Ubuntu-24.04", "tags": ["windows_laptops", "windows_desktops"]}' + +curl -X POST https://landscape.canonical.com/api/v2/child-instance-profiles \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d "{\"title\": \"Customized Ubuntu 24.04\", \"description\": \"The image from the store customized\", \"image_name\": \"Ubuntu-24.04\", \"cloud_init\": \"$(base64 --wrap=0 < cloud_init.yaml)\"}" + +curl -X POST https://landscape.canonical.com/api/v2/child-instance-profiles \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"title": "Custom Rootfs Image", "description": "My custom image", "image_name": "CustomUbuntu", "image_source": "https://example.com/myimage.tar.gz"}' ``` Example response: @@ -243,7 +252,10 @@ Optional parameters: Example request: ```bash -curl -X PATCH https://landscape.canonical.com/api/v2/child-instance-profiles/stock-ubuntu-2404 -H "Authorization: Bearer $JWT" -d '{"description": "The stock image from the store", "tags": ["windows_laptops"]}' +curl -X PATCH https://landscape.canonical.com/api/v2/child-instance-profiles/stock-ubuntu-2404 \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" + -d '{"description": "The stock image from the store", "tags": ["windows_laptops"]}' ``` Example response: @@ -286,7 +298,10 @@ Optional parameters: Example request: ```bash -curl -X POST https://landscape.canonical.com/api/v2/child-instance-profiles/stock-ubuntu-2404:reapply -H "Authorization: Bearer $JWT" -d '{"computer_ids": [1,2,3]}' +curl -X POST https://landscape.canonical.com/api/v2/child-instance-profiles/stock-ubuntu-2404:reapply \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"computer_ids": [1,2,3]}' ``` Example response: @@ -322,7 +337,10 @@ Required parameters: Example requests: ```sh -curl -X POST https://landscape.canonical.com/api/v2/child-instance-profiles/make-hosts-compliant -H "Authorization: Bearer $JWT" -d '{"host_computer_ids": [6, 15]}' +curl -X POST https://landscape.canonical.com/api/v2/child-instance-profiles/make-hosts-compliant \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"host_computer_ids": [6, 15]}' ``` Example response: diff --git a/docs/reference/api/rest-api-endpoints/computers.md b/docs/reference/api/rest-api-endpoints/computers.md index b328a5d2..dec0d398 100644 --- a/docs/reference/api/rest-api-endpoints/computers.md +++ b/docs/reference/api/rest-api-endpoints/computers.md @@ -36,6 +36,7 @@ Query parameters: - `profile::`: Instances associated with the specified profile. The `` must be one of `security`, `script`, `repository`, `package`, `upgrade`, `reboot`, `removal`, or `wsl`. The `` is the database id of the profile. - `profile:security::`: Instances associated with the specified USG security profile with the indicated last audit result. The `` is the database id of the profile. The `` must be one of `pass`, `fail`, or `in-progress`. - `profile:wsl::`: Instances associated with the specified WSL Child Instance Profile. The `` is the database id of the profile. The `` must either be `compliant` or `noncompliant`. + - `release-upgrade:available`: (Landscape 26.04+) Search for computers with a supported Ubuntu release upgrade available. - `OR`: Search for computers matching term A or term B. The text `OR` must be in all-caps. - `NOT`: search for computers not matching the next term. The text `NOT` must be in all-caps. - `license-type:`: Instances associated with the specified ``. The `license-type` must be one of `unlicensed`, `pro`, `free_pro`, `legacy`. @@ -47,6 +48,7 @@ Query parameters: - `offset`: The offset inside the list of results. - `with_alerts`: If true, includes alert information in each computer object if that alert is active. Defaults to false. - `with_upgrades`: If true, includes how many regular and security upgrades that computer has. Defaults to false. +- `with_release_upgrades`: (Landscape 26.04+) If true, includes a `has_release_upgrades` boolean in each computer object indicating whether an Ubuntu release upgrade is available. Defaults to false. - `with_reboot_packages`: Show packages needing reboot. Defaults to false. - `with_network`: If true, include the details of all network devices attached to the computer. Defaults to false. - `with_all_network`: If true, include the details of all active and inactive network devices attached to the computer. Defaults to false. @@ -107,7 +109,7 @@ Example response: Example request: ```bash -curl -X GET https://landscape.canonical.com/api/v2/computers?query=id:1%20OR%20id:2 -H "Authorization: Bearer $JWT" -H "Authorization: Bearer $JWT" +curl -X GET https://landscape.canonical.com/api/v2/computers?query=id:1%20OR%20id:2 -H "Authorization: Bearer $JWT" ``` Example response: @@ -302,6 +304,7 @@ Query parameters: - `with_hardware`: If true, include the details of all hardware information known. - `with_network`: If true, include the details of all network devices attached to the computer. - `with_profiles`: If true, include details about all profiles the computer is associated with. +- `with_alerts`: If true, includes alert information if that alert is active. Example request: @@ -1006,7 +1009,8 @@ Example request: ```bash curl -X POST "https://landscape.canonical.com/api/v2/computers/29/restart" \ - -H "Authorization: Bearer $JWT" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" ``` Example response: @@ -1116,7 +1120,10 @@ Optional parameters: Example request: ```bash -curl -X POST "https://landscape.canonical.com/api/v2/computers/1/usergroups/update_bulk" -H "Authorization: Bearer $JWT" -d '{"action": "add", "usernames": ["john", "jane"], "groupnames": ["finance", "admin"]}' +curl -X POST "https://landscape.canonical.com/api/v2/computers/1/usergroups/update_bulk" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"action": "add", "usernames": ["john", "jane"], "groupnames": ["finance", "admin"]}' ``` Example response: @@ -1141,6 +1148,34 @@ Example response: } ``` + +## POST `/computers:delete` + +```{warning} +This endpoint is available from Landscape Server 26.04 onwards for select accounts. +``` + +Delete computers with the given computer IDs. + +Required parameters: + +- `computer_ids`: A list of computer IDs to delete with minimum length 1 and maximum length 1000 + +Optional parameters: + +- None + +Example request: + +```bash +curl -X POST "https://landscape.canonical.com/api/v2/computers:delete" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"computer_ids": [1, 2, 3]}' +``` + +This endpoint returns an empty response. + ## GET `/computers//users//groups` Get all the groups for the provided username on the given computer ID. @@ -1264,7 +1299,10 @@ Required parameters: Example request: ```bash -curl -X POST "https://landscape.canonical.com/api/v2/computers/29/archive" -H "Authorization: Bearer $JWT" -d '{"computer_title": "test computer"}' +curl -X POST "https://landscape.canonical.com/api/v2/computers/29/archive" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"computer_title": "test computer"}' ``` Example response: @@ -1301,7 +1339,10 @@ Required parameters: Example request: ```bash -curl -X POST https://landscape.canonical.com/api/v2/computers/20/delete-children -H "Authorization: Bearer $JWT" -d '{"computer_names": ["Ubuntu-24.04", "Focal WSL"]}' +curl -X POST https://landscape.canonical.com/api/v2/computers/20/delete-children \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"computer_names": ["Ubuntu-24.04", "Focal WSL"]}' ``` Example response: @@ -1346,7 +1387,10 @@ Required parameters: Example request: ```bash -curl -X POST "https://landscape.canonical.com/api/v2/computers/29/sanitize" -H "Authorization: Bearer $JWT" -d '{"computer_title": "test computer"}' +curl -X POST "https://landscape.canonical.com/api/v2/computers/29/sanitize" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"computer_title": "test computer"}' ``` Example response: @@ -1371,3 +1415,106 @@ Example response: "type": "ActivityGroup" } ``` + +```{note} +This endpoint is available starting in Landscape 26.04 LTS. +``` + +## GET `/computers/release-upgrade-targets` + +Get the maximum release-upgrade target for each provided computer. +A release upgrade moves a machine to a newer Ubuntu release. An upgrade is considered available if the computer meets the minimum supported OS version and its local upgrade policy allows the move. + +Path parameters: + +- None + +Query parameters: + +- `computer_ids`: A comma-separated list of computer IDs. + +Example request: + +```bash +curl -X GET "https://landscape.canonical.com/api/v2/computers/release-upgrade-targets?computer_ids=1,2" -H "Authorization: Bearer $JWT" +``` + +Example response: + +```json +{ + "results": [ + { + "computer_id": 1, + "computer_title": "Application Server 1", + "current_release_name": "Ubuntu 18.04 LTS", + "current_release_version": "18.04", + "target_release_code_name": "jammy", + "target_release_name": "Jammy Jellyfish", + "target_release_version": "22.04", + "reason_code": null, + "reason_detail": null + }, + { + "computer_id": 2, + "computer_title": "Application Server 2", + "current_release_name": "Ubuntu 11.04", + "current_release_version": "11.04", + "target_release_code_name": null, + "target_release_name": null, + "target_release_version": null, + "reason_code": "no_upgrade_target", + "reason_detail": "No release upgrades are available." + } + ] +} +``` + +```{note} +This endpoint is available starting in Landscape 26.04 LTS +``` + +## POST `/computers/release-upgrades` + +Create a release-upgrade activity for the provided computers, which upgrades their operating system to the next available Ubuntu release. + +Path parameters: + +- None + +Required parameters: + +- `computer_ids`: A list of computer IDs. + +Example request: + +```bash +curl -X POST "https://landscape.canonical.com/api/v2/computers/release-upgrades" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"computer_ids": [1, 2]}' +``` + +Example response: + +```json +{ + "activity_status": "undelivered", + "approval_time": null, + "completion_time": null, + "creation_time": "2026-03-02T12:00:00Z", + "creator": { + "email": "john@example.com", + "id": 1, + "name": "John Smith" + }, + "deliver_delay_window": 0, + "id": 98765, + "parent_id": null, + "result_code": null, + "result_text": null, + "summary": "Release upgrade computers", + "type": "ActivityGroup" +} +``` + diff --git a/docs/reference/api/rest-api-endpoints/credentials.md b/docs/reference/api/rest-api-endpoints/credentials.md index 15028623..35c153da 100644 --- a/docs/reference/api/rest-api-endpoints/credentials.md +++ b/docs/reference/api/rest-api-endpoints/credentials.md @@ -65,7 +65,10 @@ Optional parameters: Example request: ```bash -curl -X POST "https://landscape.canonical.com/api/v2/credentials" -H "Authorization: Bearer $JWT" -d '{"account": "onward"}' +curl -X POST "https://landscape.canonical.com/api/v2/credentials" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"account": "onward"}' ``` Example response: diff --git a/docs/reference/api/rest-api-endpoints/gpg-key.md b/docs/reference/api/rest-api-endpoints/gpg-key.md index e9a012bd..fe8cc892 100644 --- a/docs/reference/api/rest-api-endpoints/gpg-key.md +++ b/docs/reference/api/rest-api-endpoints/gpg-key.md @@ -25,10 +25,13 @@ Optional parameters: Example request: ```bash -curl -X POST -H "Authorization: Bearer $JWT" -d '{ -"name": "gpg-mirror-key", -"material": "$(cat mirror-key.asc)" -}' https://landscape.canonical.com/api/v2/gpg-key +curl -X POST https://landscape.canonical.com/api/v2/gpg-key \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{ + "name": "gpg-mirror-key", + "material": "$(cat mirror-key.asc)" + }' ``` Example response: diff --git a/docs/reference/api/rest-api-endpoints/invitations.md b/docs/reference/api/rest-api-endpoints/invitations.md index b93875b8..9585bb65 100644 --- a/docs/reference/api/rest-api-endpoints/invitations.md +++ b/docs/reference/api/rest-api-endpoints/invitations.md @@ -19,7 +19,10 @@ Required parameters: Example request: ```bash -curl -X POST "https://landscape.canonical.com/api/v2/accept-invitation" -H "Authorization: Bearer $JWT" -d '{"invitation_id": "rqRmwFduPFTM1uy5cO0tOSovS4KNGG"}' +curl -X POST "https://landscape.canonical.com/api/v2/accept-invitation" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"invitation_id": "rqRmwFduPFTM1uy5cO0tOSovS4KNGG"}' ``` Example response: @@ -93,7 +96,10 @@ Optional parameters: Example request: ```bash -curl -X POST "https://landscape.canonical.com/api/v2/invitations" -H "Authorization: Bearer $JWT" -d '{"name": "Bobby", "email": "bobby@ubuntu.com", "roles": ["Auditor", "SupportAnalyst"]}' +curl -X POST "https://landscape.canonical.com/api/v2/invitations" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"name": "Bobby", "email": "bobby@ubuntu.com", "roles": ["Auditor", "SupportAnalyst"]}' ``` Example response: @@ -165,5 +171,8 @@ Required parameters: Example request: ```bash -curl -X POST "https://landscape.canonical.com/api/v2/reject-invitation" -H "Authorization: Bearer $JWT" -d '{"invitation_id": "rqRmwFduPFTM1uy5cO0tOSovS4KNGG"}' +curl -X POST "https://landscape.canonical.com/api/v2/reject-invitation" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"invitation_id": "rqRmwFduPFTM1uy5cO0tOSovS4KNGG"}' ``` diff --git a/docs/reference/api/rest-api-endpoints/license-management.md b/docs/reference/api/rest-api-endpoints/license-management.md index 90978e7e..f61361d2 100644 --- a/docs/reference/api/rest-api-endpoints/license-management.md +++ b/docs/reference/api/rest-api-endpoints/license-management.md @@ -25,7 +25,10 @@ Optional parameters: Example request: ```bash -curl -X POST https://landscape.example.com/api/v2/attach-token -H "Authorization: Bearer $JWT" -d '{"computer_ids": [1, 2], "token": ""}' +curl -X POST https://landscape.example.com/api/v2/attach-token \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"computer_ids": [1, 2], "token": ""}' ``` Example response: @@ -76,7 +79,10 @@ Optional parameters: Example request: ```bash -curl -X POST https://landscape.example.com/api/v2/attach-token -H "Authorization: Bearer $JWT" -d '{"computer_ids": [1, 2]}' +curl -X POST https://landscape.example.com/api/v2/attach-token \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"computer_ids": [1, 2]}' ``` Example response: diff --git a/docs/reference/api/rest-api-endpoints/livepatch.md b/docs/reference/api/rest-api-endpoints/livepatch.md index f410a08d..a0be840b 100644 --- a/docs/reference/api/rest-api-endpoints/livepatch.md +++ b/docs/reference/api/rest-api-endpoints/livepatch.md @@ -210,8 +210,9 @@ Example request: ```bash curl -X POST "https://landscape.canonical.com/api/v2/computers/29/kernel/upgrade" \ - -H "Authorization: Bearer $JWT" \ - -d "kernel_package_id=1122" + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d "kernel_package_id=1122" ``` Example response: @@ -259,8 +260,9 @@ Example request: ```bash curl -X POST "https://landscape.canonical.com/api/v2/computers/29/kernel/downgrade" \ - -H "Authorization: Bearer $JWT" \ - -d "kernel_package_id=1122" + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d "kernel_package_id=1122" ``` Example response: diff --git a/docs/reference/api/rest-api-endpoints/login.md b/docs/reference/api/rest-api-endpoints/login.md index 74691825..4e329388 100644 --- a/docs/reference/api/rest-api-endpoints/login.md +++ b/docs/reference/api/rest-api-endpoints/login.md @@ -13,17 +13,32 @@ Login to the REST API with a email address and password. Required parameters: -- `email` - `password` Optional parameters: - `account` +- `email` +- `expiry_minutes` +- `identity` +- `invitation_id` + +Exactly one of `email` or `identity` must be passed. Pass `email` for standard email/password authentication. For PAM authentication, pass `identity` instead. + +Example request: + +```bash +curl -X POST "https://landscape.canonical.com/api/v2/login" \ + -H "Content-Type: application/json" \ + -d '{"email": "john@example.com", "password": "pwd", "account": "onward"}' +``` Example request: ```bash -curl -X POST "https://landscape.canonical.com/api/v2/login" -d '{"email": "john@example.com", "password": "pwd", "account": "onward"}' +curl -X POST "https://landscape.canonical.com/api/v2/login" \ + -H "Content-Type: application/json" \ + -d '{"identity": "john", "password": "pwd", "account": "onward"}' ``` Example response: @@ -67,8 +82,8 @@ Example request: ```bash curl -X POST "https://landscape.canonical.com/api/v2/login/access-key" \ --H "Content-Type: application/json" \ --d '{"access_key": "3AS5YX98J8QI9AZ8OS0V", "secret_key": "avlhg23w9HyOWOA1FMzHmrBaB8a97zafzJOApfF2"}' + -H "Content-Type: application/json" \ + -d '{"access_key": "3AS5YX98J8QI9AZ8OS0V", "secret_key": "avlhg23w9HyOWOA1FMzHmrBaB8a97zafzJOApfF2"}' ``` Example response: diff --git a/docs/reference/api/rest-api-endpoints/password.md b/docs/reference/api/rest-api-endpoints/password.md index 20b2d296..24652545 100644 --- a/docs/reference/api/rest-api-endpoints/password.md +++ b/docs/reference/api/rest-api-endpoints/password.md @@ -27,5 +27,8 @@ Optional parameters: Example request: ```bash -curl -X PUT "https://landscape.canonical.com/api/v2/password" -d '{"password": "pwd", "new_password": "more_secure_pwd"}' -H Authorization:"Bearer $JWT" +curl -X PUT "https://landscape.canonical.com/api/v2/password" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"password": "pwd", "new_password": "more_secure_pwd"}' ``` diff --git a/docs/reference/api/rest-api-endpoints/person.md b/docs/reference/api/rest-api-endpoints/person.md index 79735dc5..0bc73ba1 100644 --- a/docs/reference/api/rest-api-endpoints/person.md +++ b/docs/reference/api/rest-api-endpoints/person.md @@ -62,9 +62,10 @@ Optional parameters: Example request: ```bash -curl -X POST -H "Authorization: Bearer $JWT" -d '{ - "name": "John Allen Smith" - }' https://landscape.canonical.com/api/v2/person +curl -X POST https://landscape.canonical.com/api/v2/person \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"name": "John Allen Smith"}' ``` Example response: @@ -85,3 +86,27 @@ Example response: "preferred_account": null } ``` + +## POST `/person:test-email` + +Sends a test email to the currently logged-in user's email address to verify the mail queue configuration. + +Path parameters: + +- None + +Query parameters: + +- None + +Example request: + +```bash +curl -X POST https://landscape.canonical.com/api/v2/person:test-email \ + -H "Authorization: Bearer $JWT" +``` + +Response codes: + +- `204`: Email successfully queued. +- `502`: Failed to queue the test email. Check your mail queue configuration. diff --git a/docs/reference/api/rest-api-endpoints/processes.md b/docs/reference/api/rest-api-endpoints/processes.md index c40ea47a..f9ede5eb 100644 --- a/docs/reference/api/rest-api-endpoints/processes.md +++ b/docs/reference/api/rest-api-endpoints/processes.md @@ -24,14 +24,10 @@ Optional parameters: Example request: ```bash -curl -X POST \ +curl -X POST https://landscape.canonical.com/api/v2/processes/kill \ + -H "Content-Type: application/json" \ -H "Authorization: Bearer $JWT" \ - -d '{ - "computer_id": 1, - "pids": [1] - }' \ - https://landscape.canonical.com/api/v2/processes/kill - + -d '{"computer_id": 1, "pids": [1]}' ``` Example response: diff --git a/docs/reference/api/rest-api-endpoints/reboot-profiles.md b/docs/reference/api/rest-api-endpoints/reboot-profiles.md index 61bce0b2..1526e039 100644 --- a/docs/reference/api/rest-api-endpoints/reboot-profiles.md +++ b/docs/reference/api/rest-api-endpoints/reboot-profiles.md @@ -33,15 +33,15 @@ The scheduled time (`at_hour` and `at_minute`) is interpreted in **UTC**. Example request: ```bash -curl -X POST "https://landscape.canonical.com/api/v2/rebootprofiles" \ - -H "Authorization: Bearer $JWT" \ +curl -X POST "https://landscape.canonical.com/api/v2/rebootprofiles" \ -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ -d '{ "title": "rebootprofile1", "on_days": ["mo"], "at_hour": 21, "tags": ["laptop"] -}' + }' ``` Example response: @@ -91,8 +91,8 @@ Example request: ```bash curl -X PATCH "https://landscape.canonical.com/api/v2/rebootprofiles/11" \ - -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ -d '{ "title": "newtitle" }' diff --git a/docs/reference/api/rest-api-endpoints/recovery-key.md b/docs/reference/api/rest-api-endpoints/recovery-key.md new file mode 100644 index 00000000..51d2c08a --- /dev/null +++ b/docs/reference/api/rest-api-endpoints/recovery-key.md @@ -0,0 +1,94 @@ +(reference-rest-api-recovery-key)= + +# FDE Recovery Key + +```{note} +These endpoints will only work with a computer with TPM-backed full disk encryption. +``` + +## GET `/computers//recovery-key` + +Gets the recovery key for a computer if one was created with Landscape. If an activity to generate the recovery key is in progress, additionally returns the state of the latest recovery key generation activity. + +Path parameters: + +- `computer_id`: The ID assigned to a specific computer. + +Query parameters: + +- None + +Example request: + +```bash +curl -X GET "https://landscape.canonical.com/api/v2/computers/23/recovery-key" -H "Authorization: Bearer $JWT" +``` + +Example response: + +```json +{ + "activity": { + "activity_status": "undelivered", + "approval_time": null, + "completion_time": null, + "creation_time": "2026-01-13T21:57:57Z", + "creator": { + "email": "john@example.com", + "id": 1, + "name": "John Smith" + }, + "deliver_delay_window": 0, + "id": 115, + "parent_id": null, + "result_code": null, + "result_text": null, + "summary": "Request computer 23 to generate a FDE recovery key.", + "type": "ActivityGroup" + }, + "fde_recovery_key": "12345-12345-12345-12345-12345-12345-12345-12345" +} +``` + +## POST `/computers//recovery-key:generate` + +Generates a recovery key for a computer. This will fail if an activity to generate a recovery key for the computer is in progress. + +Path parameters: + +- `computer_id`: The ID assigned to a specific computer. + +Query parameters: + +- `force`: If true, an activity will be created even if another activity to generate the recovery key is in progress. + +Example request: + +```bash +curl -X POST "https://landscape.canonical.com/api/v2/computers/23/recovery-key:generate" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" +``` + +Example response: + +```json +{ + "activity_status": "undelivered", + "approval_time": null, + "completion_time": null, + "creation_time": "2026-01-13T21:57:57Z", + "creator": { + "email": "john@example.com", + "id": 1, + "name": "John Smith" + }, + "deliver_delay_window": 0, + "id": 115, + "parent_id": null, + "result_code": null, + "result_text": null, + "summary": "Request computer 23 to generate a FDE recovery key.", + "type": "ActivityGroup" +} +``` diff --git a/docs/reference/api/rest-api-endpoints/repositories.md b/docs/reference/api/rest-api-endpoints/repositories.md index aa821f44..ae5e649b 100644 --- a/docs/reference/api/rest-api-endpoints/repositories.md +++ b/docs/reference/api/rest-api-endpoints/repositories.md @@ -66,3 +66,84 @@ Example request: ```bash curl -X DELETE https://landscape.canonical.com/api/v2/repository/apt-source/12 -H "Authorization: Bearer $JWT" ``` + +## POST `/repository/series` + +Create a series associated with a distribution in the account. Optionally create and associate pockets with this series. + +Required parameters: + +- `distribution`: The name of the distribution to create the series in. +- `name`: The name of the series. It must be unique within series within the distribution, start with an alphanumeric character and only contain lowercase letters, numbers, and `-` or `+` signs. + +Optional parameters: + +- `gpg_key`: The name of the GPG key to use to sign packages lists of the created pockets. This parameter is required if a pocket is specified. +- `include_udeb`: Whether the pocket should include selected components also for .udeb packages (debian-installer). It’s `false` by default. +- `mirror_gpg_key`: The name of the GPG key to use to verify the mirrored repositories for created pockets. If none is given, the stock Ubuntu archive one will be used. +- `mirror_series`: The remote series to mirror. If none is given, the name of the series will be used. If a pockets parameter also passed, each of the created pockets will mirror the relevant `dists/-` repository of the remote archive. +- `mirror_uri`: The URI to mirror for the created pockets. This parameter is required if a pocket is specified. +- `origin`: The origin of the pocket. +- `pockets`: An array of pocket objects. Each object defines a pocket to be created in the series. They will be in mirror mode by default. If this field is provided, each entry in the array must include: + - `name`: The pocket name. + - `components`: The list of components for this pocket. + - `architectures`: The list of architectures for this pocket. + +Example request: + +```bash +curl -X POST https://landscape.canonical.com/api/v2/repository/series \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{ + "name": "oracular", + "distribution": "ubuntu", + "pockets": [ + { + "name": "proposed", + "components": ["main"], + "architectures": ["amd64"] + } + ], + "mirror_uri": "http://archive.ubuntu.com", + "gpg_key": "my-key" + }' +``` + +Example output: + +```json +{ + "name": "oracular", + "creation_time": "2025-12-11T01:18:18Z", + "pockets": [{ + "id": 35, + "name": "proposed", + "creation_time": "2025-12-11T01:18:18Z", + "mode": "mirror", + "gpg_key": { + "id": 1, + "name": "my-key", + "key_id": "446733C2526084AB", + "fingerprint": "cd4b:22a6:a06d:7fc8:bd39:0b7e:4467:33c2:5260:84ab", + "has_secret": true + }, + "components": ["main"], + "architectures": ["amd64"], + "include_udeb": false, + "apt_source_line": "deb http://landscape.canonical.com/repository/myaccount/ubuntu oracular-proposed main", + "series": { + "name": "oracular", + "creation_time": "2025-12-11T01:18:18Z" + }, + "distribution": { + "name": "ubuntu", + "access_group": "global", + "creation_time": "2025-12-10T00:59:43Z" + }, + "package_count": 0, + "mirror_uri": "http://archive.ubuntu.com", + "mirror_suite": "oracular-proposed" + }] +} +``` diff --git a/docs/reference/api/rest-api-endpoints/repository-profiles.md b/docs/reference/api/rest-api-endpoints/repository-profiles.md index 835a077d..588edbb0 100644 --- a/docs/reference/api/rest-api-endpoints/repository-profiles.md +++ b/docs/reference/api/rest-api-endpoints/repository-profiles.md @@ -28,8 +28,8 @@ Example request: ```bash curl -X POST "https://landscape.canonical.com/api/v2/repositoryprofiles" \ - -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ -d '{ "title": "Noble Repo Profile", "description": "Repository profile for noble instances", @@ -105,8 +105,8 @@ Example request: ```bash curl -X PUT "https://landscape.canonical.com/api/v2/repositoryprofiles/noble-repo-profile" \ - -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ -d '{ "title": "Noble Repo Profile", "description": "Updated description", diff --git a/docs/reference/api/rest-api-endpoints/script_profiles.md b/docs/reference/api/rest-api-endpoints/script_profiles.md index a3a188ac..f1c6fd20 100644 --- a/docs/reference/api/rest-api-endpoints/script_profiles.md +++ b/docs/reference/api/rest-api-endpoints/script_profiles.md @@ -211,19 +211,22 @@ Required parameters: Example request: ```bash -curl -X POST "https://landscape.canonical.com/api/v2/script-profiles" -H "Authorization: Bearer $JWT" -d ' -{ - "title": "Optics and Design", - "username": "root", - "time_limit": 360, - "script_id": 35, - "tags": [], - "all_computers": true, - "trigger": { - "trigger_type": "event", - "event_type": "post_enrollment" - } -}' +curl -X POST "https://landscape.canonical.com/api/v2/script-profiles" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d ' + { + "title": "Optics and Design", + "username": "root", + "time_limit": 360, + "script_id": 35, + "tags": [], + "all_computers": true, + "trigger": { + "trigger_type": "event", + "event_type": "post_enrollment" + } + }' ``` Example response: @@ -299,15 +302,17 @@ Optional parameters: Example request: ```bash -curl -X PATCH "https://landscape.canonical.com/api/v2/script-profiles/176892" -H "Authorization: Bearer $JWT" -d ' -{ - "title": "Macrodata Refinement", - "username": "landscape", - "time_limit": 270, - "tags": ["server"], - "all_computers": false -}' - +curl -X PATCH "https://landscape.canonical.com/api/v2/script-profiles/176892" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d ' + { + "title": "Macrodata Refinement", + "username": "landscape", + "time_limit": 270, + "tags": ["server"], + "all_computers": false + }' ``` Example response: @@ -355,7 +360,9 @@ Path parameters: Example request: ```bash -curl -X POST "https://landscape.canonical.com/api/v2/script-profiles/176892:archive" -H "Authorization: Bearer $JWT" +curl -X POST "https://landscape.canonical.com/api/v2/script-profiles/176892:archive" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" ``` ## GET `/script-profiles//activities` @@ -371,7 +378,9 @@ Path parameters: Example request: ```bash -curl -X POST "https://landscape.canonical.com/api/v2/script-profiles/176892/activities" -H "Authorization: Bearer $JWT" +curl -X POST "https://landscape.canonical.com/api/v2/script-profiles/176892/activities" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" ``` Example response: @@ -410,7 +419,9 @@ Get account wide limits associated with script profiles. Example request: ```bash -curl -X POST "https://landscape.canonical.com/api/v2/script-profile-limits" -H "Authorization: Bearer $JWT" +curl -X POST "https://landscape.canonical.com/api/v2/script-profile-limits" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" ``` Example response: diff --git a/docs/reference/api/rest-api-endpoints/scripts.md b/docs/reference/api/rest-api-endpoints/scripts.md index 34a36447..b83bbced 100644 --- a/docs/reference/api/rest-api-endpoints/scripts.md +++ b/docs/reference/api/rest-api-endpoints/scripts.md @@ -343,7 +343,9 @@ Path parameters: Example request: ```bash -curl -X POST "https://landscape.canonical.com/api/v2/scripts/176892:archive" -H "Authorization: Bearer $JWT" +curl -X POST "https://landscape.canonical.com/api/v2/scripts/176892:archive" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" ``` ## POST `/scripts/:redact` @@ -359,5 +361,7 @@ Path parameters: Example request: ```bash -curl -X POST "https://landscape.canonical.com/api/v2/scripts/176892:redact" -H "Authorization: Bearer $JWT" +curl -X POST "https://landscape.canonical.com/api/v2/scripts/176892:redact" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" ``` diff --git a/docs/reference/api/rest-api-endpoints/security_profiles.md b/docs/reference/api/rest-api-endpoints/security_profiles.md index 8519365d..d3c3ff07 100644 --- a/docs/reference/api/rest-api-endpoints/security_profiles.md +++ b/docs/reference/api/rest-api-endpoints/security_profiles.md @@ -126,19 +126,22 @@ Optional parameters: Example request: ```bash -curl -X POST "https://landscape.canonical.com/api/v2/security-profiles" -H "Authorization: Bearer $JWT" -d ' -{ - "benchmark": "disa_stig", - "mode": "audit", - "schedule": "RRULE:FREQ=WEEKLY", - "title": "Macrodata Refinement Terminals", - "start_date": "2025-03-29T12:00:00Z", - "access_group": "mdr-terminals", - "all_computers": false, - "restart_deliver_delay": 2, - "restart_deliver_delay_window": 10, - "tags": ["mdr"] -}' +curl -X POST "https://landscape.canonical.com/api/v2/security-profiles" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d ' + { + "benchmark": "disa_stig", + "mode": "audit", + "schedule": "RRULE:FREQ=WEEKLY", + "title": "Macrodata Refinement Terminals", + "start_date": "2025-03-29T12:00:00Z", + "access_group": "mdr-terminals", + "all_computers": false, + "restart_deliver_delay": 2, + "restart_deliver_delay_window": 10, + "tags": ["mdr"] + }' ``` Example response, the profile's state: @@ -198,12 +201,15 @@ Optional parameters: Example request: ```bash -curl -X PATCH "https://landscape.canonical.com/api/v2/security-profiles/1" -H "Authorization: Bearer $JWT" -d ' -{ - "schedule": "RRULE:FREQ=WEEKLY;BYDAY=MO", - "title": "Macrodata Refinement Terminals Updated", - "restart_deliver_delay_window": 300 -}' +curl -X PATCH "https://landscape.canonical.com/api/v2/security-profiles/1" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d ' + { + "schedule": "RRULE:FREQ=WEEKLY;BYDAY=MO", + "title": "Macrodata Refinement Terminals Updated", + "restart_deliver_delay_window": 300 + }' ``` Example response: @@ -253,7 +259,9 @@ Path parameters: Example request: ```bash -curl -X POST "https://landscape.canonical.com/api/v2/security-profiles/1:archive" -H "Authorization: Bearer $JWT" +curl -X POST "https://landscape.canonical.com/api/v2/security-profiles/1:archive" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" ``` Example response: @@ -303,7 +311,9 @@ Path parameters: Example request: ```bash -curl -X POST "https://landscape.canonical.com/api/v2/security-profiles/1:execute" -H "Authorization: Bearer $JWT" +curl -X POST "https://landscape.canonical.com/api/v2/security-profiles/1:execute" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" ``` Example response: diff --git a/docs/reference/api/rest-api-endpoints/snaps.md b/docs/reference/api/rest-api-endpoints/snaps.md index a5c4f13b..ccbe139e 100644 --- a/docs/reference/api/rest-api-endpoints/snaps.md +++ b/docs/reference/api/rest-api-endpoints/snaps.md @@ -25,14 +25,17 @@ Optional parameters: Example request: ```bash -curl -X POST -H "Authorization: Bearer $JWT" -d '{ - "action": "install", - "computer_ids": [23], - "snaps": [ +curl -X POST https://landscape.canonical.com/api/v2/snaps \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{ + "action": "install", + "computer_ids": [23], + "snaps": [ {"name": "hello"}, {"name": "spotify"} - ] - }' https://landscape.canonical.com/api/v2/snaps + ] + }' ``` Example response: diff --git a/docs/reference/api/rest-api-endpoints/switch-account.md b/docs/reference/api/rest-api-endpoints/switch-account.md index 55e34285..293448b4 100644 --- a/docs/reference/api/rest-api-endpoints/switch-account.md +++ b/docs/reference/api/rest-api-endpoints/switch-account.md @@ -22,9 +22,12 @@ Optional parameters: Example request: ```bash -curl -X POST -H "Authorization: Bearer $JWT" -d '{ - "account_name": "upside" - }' https://landscape.canonical.com/api/v2/switch-account +curl -X POST https://landscape.canonical.com/api/v2/switch-account \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{ + "account_name": "upside" + }' ``` Example response: diff --git a/docs/reference/api/rest-api-endpoints/tokens.md b/docs/reference/api/rest-api-endpoints/tokens.md index b18ea17c..94be222f 100644 --- a/docs/reference/api/rest-api-endpoints/tokens.md +++ b/docs/reference/api/rest-api-endpoints/tokens.md @@ -24,7 +24,9 @@ Query parameters: Example request: ```bash -curl -X POST https://landscape.canonical.com/api/v2/tokens/invalidate-account -H "Authorization: Bearer $JWT" +curl -X POST https://landscape.canonical.com/api/v2/tokens/invalidate-account \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" ``` This endpoint returns an empty response. @@ -44,7 +46,9 @@ Query parameters: Example request: ```bash -curl -X POST https://landscape.canonical.com/api/v2/tokens/invalidate-me -H "Authorization: Bearer $JWT" +curl -X POST https://landscape.canonical.com/api/v2/tokens/invalidate-me \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" ``` This endpoint returns an empty response. @@ -64,7 +68,9 @@ Query parameters: Example request: ```bash -curl -X POST https://landscape.canonical.com/api/v2/tokens/invalidate-system -H "Authorization: Bearer $JWT" +curl -X POST https://landscape.canonical.com/api/v2/tokens/invalidate-system \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" ``` This endpoint returns an empty response. @@ -84,7 +90,9 @@ Query parameters: Example request: ```bash -curl -X POST https://landscape.canonical.com/api/v2/tokens/invalidate-account/account/my-account -H "Authorization: Bearer $JWT" +curl -X POST https://landscape.canonical.com/api/v2/tokens/invalidate-account/account/my-account \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" ``` This endpoint returns an empty response. diff --git a/docs/reference/api/rest-api-endpoints/users.md b/docs/reference/api/rest-api-endpoints/users.md index 46578140..9b162ee5 100644 --- a/docs/reference/api/rest-api-endpoints/users.md +++ b/docs/reference/api/rest-api-endpoints/users.md @@ -148,13 +148,13 @@ Optional parameters: Example request: ```bash -curl -X POST \ +curl -X POST https://landscape.canonical.com/api/v2/users/lock \ + -H "Content-Type: application/json" \ -H "Authorization: Bearer $JWT" \ -d '{ - "computer_ids": [1], - "usernames": ["john"] - }' \ - https://landscape.canonical.com/api/v2/users/lock + "computer_ids": [1], + "usernames": ["john"] + }' ``` Example response: @@ -195,10 +195,13 @@ Optional parameters: Example request: ```bash -curl -X POST -H "Authorization: Bearer $JWT" -d '{ - "computer_ids": [1], - "usernames": ["john"] - }' https://landscape.canonical.com/api/v2/users/unlock +curl -X POST https://landscape.canonical.com/api/v2/users/unlock \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{ + "computer_ids": [1], + "usernames": ["john"] + }' ``` diff --git a/docs/reference/api/rest-api-endpoints/wsl.md b/docs/reference/api/rest-api-endpoints/wsl.md index 8ceaaaad..c6030adb 100644 --- a/docs/reference/api/rest-api-endpoints/wsl.md +++ b/docs/reference/api/rest-api-endpoints/wsl.md @@ -152,9 +152,15 @@ Optional parameters: Example requests: ```bash -curl -X POST https://landscape.canonical.com/api/v2/computers/20/children -H "Authorization: Bearer $JWT" -d '{"computer_name": "Ubuntu-24.04"}' - -curl -X POST https://landscape.canonical.com/api/v2/computers/20/children -H "Authorization: Bearer $JWT" -d "{\"computer_name\": \"Ubuntu-24.04\", \"cloud_init\": \"$(base64 --wrap=0 < ~/cloud_init.yaml)\"}" +curl -X POST https://landscape.canonical.com/api/v2/computers/20/children \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"computer_name": "Ubuntu-24.04"}' + +curl -X POST https://landscape.canonical.com/api/v2/computers/20/children \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d "{\"computer_name\": \"Ubuntu-24.04\", \"cloud_init\": \"$(base64 --wrap=0 < ~/cloud_init.yaml)\"}" ``` Example response: @@ -182,7 +188,10 @@ Example response: Example request: ```bash -curl -X POST https://landscape.canonical.com/api/v2/computers/20/children -H "Authorization: Bearer $JWT" -d '{"computer_name": "Custom-WSL-Image", "cloud_init": "", "rootfs_url": "https://example.com/custom_wsl_image.tar.gz"}' +curl -X POST https://landscape.canonical.com/api/v2/computers/20/children \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"computer_name": "Custom-WSL-Image", "cloud_init": "", "rootfs_url": "https://example.com/custom_wsl_image.tar.gz"}' ``` Example response: @@ -228,7 +237,10 @@ Optional parameters: Example request: ```bash -curl -X POST -H "Authorization: Bearer $JWT" "https://landscape.canonical.com/api/v2/computers/6/delete-children" -d '{"computer_names": ["child_one", "child_two"]}' +curl -X POST "https://landscape.canonical.com/api/v2/computers/6/delete-children" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $JWT" \ + -d '{"computer_names": ["child_one", "child_two"]}' ``` Example response: diff --git a/docs/reference/charm/haproxy-route-endpoints.md b/docs/reference/charm/haproxy-route-endpoints.md new file mode 100644 index 00000000..dccecce7 --- /dev/null +++ b/docs/reference/charm/haproxy-route-endpoints.md @@ -0,0 +1,56 @@ +(reference-charm-haproxy-route-endpoints)= + +# HAProxy route endpoints + +The Landscape Server charm exposes 8 relation endpoints using the `haproxy-route` interface. Each endpoint routes traffic for a specific Landscape service. All endpoints must be integrated with the HAProxy charm (`2.8/x`). + +## Relation endpoints + +| Endpoint | Service | URL paths | Protocol | Default backend port(s) | +| --------------------------------------- | ------------------------- | -------------------------------- | -------- | ------------------------------------------ | +| `appserver-haproxy-route` | Landscape app server | `/`, `/hash-id-databases` | HTTP | `appserver_base_port` (default `8080`) | +| `pingserver-haproxy-route` | Ping server | `/ping` | HTTP | `pingserver_base_port` (default `8070`) | +| `message-server-haproxy-route` | Message server | `/message-system`, `/attachment` | HTTP | `message_server_base_port` (default `8090`) | +| `api-haproxy-route` | REST API | `/api` | HTTP | `api_base_port` (default `9080`) | +| `package-upload-haproxy-route` | Package upload | `/upload` | HTTP | `package_upload_base_port` (default `9100`) | +| `repository-haproxy-route` | Repository mirror | `/repository` | HTTP | `appserver_base_port` (default `8080`) | +| `hostagent-messenger-haproxy-route` | Host agent gRPC messenger | (gRPC) | gRPC | `hostagent_server_base_port` (default `50052`) | +| `ubuntu-installer-attach-haproxy-route` | Ubuntu Installer Attach | (gRPC) | gRPC | `ubuntu_installer_attach_base_port` (default `53354`) | + +Backend ports are configurable via the charm config options shown above (e.g. `juju config landscape-server appserver_base_port=8080`). For multi-worker services (`appserver`, `pingserver`, `message-server`, `api`), the charm allocates one port per worker starting at the base port — e.g., with `worker_counts=2` and `appserver_base_port=8080`, ports `8080` and `8081` are used. Metrics paths (e.g. `/ping/metrics`, `/api/metrics`) are denied on all endpoints. + +## Integration + +Integrate all endpoints with the HAProxy charm: + +```bash +juju integrate landscape-server:appserver-haproxy-route haproxy:haproxy-route +juju integrate landscape-server:pingserver-haproxy-route haproxy:haproxy-route +juju integrate landscape-server:message-server-haproxy-route haproxy:haproxy-route +juju integrate landscape-server:api-haproxy-route haproxy:haproxy-route +juju integrate landscape-server:package-upload-haproxy-route haproxy:haproxy-route +juju integrate landscape-server:repository-haproxy-route haproxy:haproxy-route +juju integrate landscape-server:hostagent-messenger-haproxy-route haproxy:haproxy-route +juju integrate landscape-server:ubuntu-installer-attach-haproxy-route haproxy:haproxy-route +``` + +The `hostagent-messenger-haproxy-route` and `ubuntu-installer-attach-haproxy-route` endpoints are only active when `enable_hostagent_messenger` and `enable_ubuntu_installer_attach` are set to `True` respectively. + +## HTTP redirect behaviour and `redirect_https` + +The `redirect_https` charm configuration option controls whether HTTP traffic is redirected to HTTPS: + +| `redirect_https` value | `appserver`, `message-server`, `api`, `package-upload` | `pingserver`, `repository` | +| ---------------------- | ------------------------------------------------------ | -------------------------- | +| `default` | HTTP redirected to HTTPS | HTTP allowed | +| `none` | HTTP allowed | HTTP allowed | +| `all` | HTTP redirected to HTTPS | HTTP redirected to HTTPS | + +`pingserver` and `repository` always allow plain HTTP under `default` and `none` because Landscape Clients and mirrors use HTTP for those paths. + +## External gRPC ports + +| Endpoint | External port | +| --------------------------------------- | ------------- | +| `hostagent-messenger-haproxy-route` | `6554` | +| `ubuntu-installer-attach-haproxy-route` | `50051` | diff --git a/docs/reference/charm/index.md b/docs/reference/charm/index.md new file mode 100644 index 00000000..eb3aeef4 --- /dev/null +++ b/docs/reference/charm/index.md @@ -0,0 +1,10 @@ +(reference-charm-index)= + +# Landscape Server Charm + +```{toctree} +:titlesonly: +:maxdepth: 2 +:glob: + +HAProxy route endpoints diff --git a/docs/reference/config/service-conf.md b/docs/reference/config/service-conf.md index e96f8dfb..b0442e63 100644 --- a/docs/reference/config/service-conf.md +++ b/docs/reference/config/service-conf.md @@ -669,6 +669,13 @@ The `[package_upload]` section contains configurations for the package upload se The `[pingserver]` section contains configurations for the `pingserver` service that communicates with registered clients, notifying the clients about available messages. In addition to the following, this section can use the {ref}`shared service settings ` and the {ref}`shared store settings `. +### `batch_size` + +- Purpose: Pings will be written to the database in batches of this configured size. +- Deprecated key name: N/A +- ENV name: `LANDSCAPE_PINGSERVER__BATCH_SIZE` +- Default: `100` + ### `database_check_interval` - Purpose: Interval in seconds to check the database for computers with outstanding messages. @@ -690,6 +697,13 @@ The `[pingserver]` section contains configurations for the `pingserver` service - ENV name: `LANDSCAPE_PINGSERVER__PING_URL` - Default: `None` +### `statement_timeout_ms` + +- Purpose: An attempt to write pings to the database will timeout after the configured time (in milliseconds) has elapsed. +- Deprecated key name: N/A +- ENV name: `LANDSCAPE_PINGSERVER__STATEMENT_TIMEOUT_MS` +- Default: `3000000` (5 minutes) + ## The `[schema]` section The `[schema]` section contains configurations for updating the database schema. In addition to the following, this section can use the {ref}`shared service settings ` and the {ref}`shared store settings `. @@ -932,7 +946,7 @@ The `[system]` section contains configurations that apply across many or all of - Purpose: Landscape Server's root URL path. - Deprecated key name: `root-url` - ENV name: `LANDSCAPE_SYSTEM__ROOT_URL` -- Default: `http://localhost:8080` +- Default: `http://localhost:8080/` ### `syslog_address` diff --git a/docs/reference/index.md b/docs/reference/index.md index 31577292..53d54c49 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -5,6 +5,7 @@ myst: --- (reference-index)= + # Reference This section includes technical reference information for Landscape Server and Landscape Client. @@ -53,6 +54,17 @@ Network requirements, ports, and firewall configurations. networking/index ``` +## Landscape Server charm + +References specifically related to the Landscape Server charm. + +```{toctree} +:titlesonly: +:maxdepth: 2 + +charm/index +``` + ## Terminology Definitions of key terms and concepts used in Landscape. diff --git a/docs/reference/release-notes/index.md b/docs/reference/release-notes/index.md index 647df0d6..1679665c 100644 --- a/docs/reference/release-notes/index.md +++ b/docs/reference/release-notes/index.md @@ -13,7 +13,6 @@ Release notes documenting new features, bug fixes, and changes across all Landsc :titlesonly: :maxdepth: 1 -what-s-new-in-beta 24-04-lts-release-notes 25.10-release-notes 25.04-release-notes diff --git a/docs/reference/release-notes/what-s-new-in-beta.md b/docs/reference/release-notes/what-s-new-in-beta.md deleted file mode 100644 index c3132391..00000000 --- a/docs/reference/release-notes/what-s-new-in-beta.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -myst: - html_meta: - description: "Latest beta features and updates for Landscape. Discover upcoming capabilities and experimental features being tested before official release." ---- - -(reference-whats-new-in-beta)= -# What's new in beta - -Here's what's new in Landscape beta: - -## Landscape Server - -No new updates. Our 25.04 release is now live, see the {ref}`reference-release-notes-25-04`. diff --git a/docs/reference/terms/access-groups.md b/docs/reference/terms/access-groups.md index dc04dcf0..8fdfd09d 100644 --- a/docs/reference/terms/access-groups.md +++ b/docs/reference/terms/access-groups.md @@ -23,6 +23,8 @@ global It's good practice to create and document a naming convention for access groups before you deploy Landscape, so that all administrators understand what constitutes an acceptable logical grouping for your organization. +**Note**: The only special characters allowed in the title of an access group are `.` and `-`. All other special characters will be stripped from the title. + ## Managing access groups ### In the new web portal diff --git a/docs/requirements.txt b/docs/requirements.txt index 669adeb7..163018ca 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -29,3 +29,6 @@ sphinxext-rediraffe # Vale dependencies vale + +# Mermaid diagrams +sphinxcontrib-mermaid diff --git a/docs/reuse/charm-ha-architecture-2604.md b/docs/reuse/charm-ha-architecture-2604.md new file mode 100644 index 00000000..6391954b --- /dev/null +++ b/docs/reuse/charm-ha-architecture-2604.md @@ -0,0 +1,20 @@ +```{mermaid} +flowchart TD + Client([Client]) + TLS[TLS Provider] + subgraph model[Juju model] + HAProxy["HAProxy
2.8/edge"] + LS0[landscape-server/0] + LS1[landscape-server/1] + LS2[landscape-server/2] + PG[(PostgreSQL)] + RMQ[RabbitMQ Server] + end + TLS -- certificates --> HAProxy + Client -- HTTPS --> HAProxy + HAProxy -- haproxy-route --> LS0 + HAProxy -- haproxy-route --> LS1 + HAProxy -- haproxy-route --> LS2 + LS0 & LS1 & LS2 --- PG + LS0 & LS1 & LS2 --- RMQ +``` diff --git a/docs/reuse/charm-ha-architecture-pre-2604.md b/docs/reuse/charm-ha-architecture-pre-2604.md new file mode 100644 index 00000000..66d8b7bf --- /dev/null +++ b/docs/reuse/charm-ha-architecture-pre-2604.md @@ -0,0 +1,18 @@ +```{mermaid} +flowchart TD + Client([Client]) + subgraph model[Juju model] + HAProxy["HAProxy
latest/stable"] + LS0[landscape-server/0] + LS1[landscape-server/1] + LS2[landscape-server/2] + PG[(PostgreSQL 14)] + RMQ[RabbitMQ Server] + end + Client -- HTTPS --> HAProxy + HAProxy -- reverseproxy --> LS0 + HAProxy -- reverseproxy --> LS1 + HAProxy -- reverseproxy --> LS2 + LS0 & LS1 & LS2 --- PG + LS0 & LS1 & LS2 --- RMQ +```