-
Notifications
You must be signed in to change notification settings - Fork 0
docs: add MkDocs documentation site with Diátaxis structure #91
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
cbc2bd3
6919013
3624a22
b09dfdd
10dbc27
bf7fd98
10bc947
b6bb381
28e340d
105870c
5248816
4c0b79a
9a7291a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| name: docs | ||
|
|
||
| on: | ||
| pull_request: | ||
| paths: | ||
| - docs/** | ||
| - mkdocs.yml | ||
| push: | ||
| branches: | ||
| - master | ||
| paths: | ||
| - docs/** | ||
| - mkdocs.yml | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: pages | ||
| cancel-in-progress: false | ||
|
|
||
| jobs: | ||
| build: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: astral-sh/setup-uv@v4 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. astral-sh/setup-uv is a third-party action pinned only to a mutable tag (@v4). Third-party actions carry additional supply-chain risk compared to GitHub-owned actions. Pin this action to a specific immutable commit SHA. |
||
| - name: Build docs | ||
| run: uv run --with mkdocs-material==9.7.6 mkdocs build --strict | ||
| - uses: actions/upload-pages-artifact@v3 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. actions/upload-pages-artifact is pinned to a mutable version tag (@V3). Pin this action to a specific immutable commit SHA. |
||
| with: | ||
| path: site | ||
|
|
||
| deploy: | ||
| if: github.event_name == 'push' && github.ref == 'refs/heads/master' | ||
| needs: build | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| pages: write | ||
| id-token: write | ||
| environment: | ||
| name: github-pages | ||
| url: ${{ steps.deployment.outputs.page_url }} | ||
| steps: | ||
| - uses: actions/configure-pages@v5 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. actions/configure-pages is pinned to a mutable version tag (@v5) and runs in a job with pages: write and id-token: write permissions. A supply-chain compromise here could enable unauthorized deployments or OIDC token abuse. Pin this action to a specific immutable commit SHA. |
||
| - id: deployment | ||
| uses: actions/deploy-pages@v4 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. actions/deploy-pages is pinned to a mutable version tag (@v4) and runs with elevated permissions (pages: write, id-token: write). This is the highest-risk unpinned action in this workflow. We strongly recommend pinning this action to a specific immutable commit SHA before merging. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,215 @@ | ||
| # Architecture | ||
|
|
||
| YACD is a Kubernetes-native development environment manager for Cardano. It is | ||
| built for people developing applications on Cardano, not for validators, stake | ||
| pool operators, or production network operators. This page explains how the | ||
| system is put together and why it is shaped the way it is. For step-by-step | ||
| instructions see the [developer](../developer/getting-started.md) and | ||
| [operator](../operator/installation.md) guides; for exhaustive field and flag | ||
| facts see the [reference](../reference/cardanonetwork.md) section. | ||
|
|
||
| ## Two artifacts: operator and CLI | ||
|
|
||
| YACD ships as two cooperating pieces: a Kubernetes operator and a companion | ||
| `yacd` CLI. The split is deliberate, and it follows the grain of Kubernetes | ||
| itself. | ||
|
|
||
| The operator owns **declarative cluster state**. It reconciles long-lived | ||
| desired state: node workloads, generated or fetched configuration, genesis | ||
| material, supporting services, persistent volumes, Secrets, ConfigMaps, | ||
| Services, and the status that reports whether all of that is healthy. If you can | ||
| sensibly express it as "this environment should exist, with these services | ||
| enabled," it belongs to the operator. The operator watches its custom resources | ||
| across namespaces and continuously drives the cluster toward the spec. | ||
|
|
||
| The CLI owns **imperative developer workflow**. It compiles a single | ||
| developer-facing config file into the Kubernetes resources the operator | ||
| consumes, applies them, waits for readiness, prints connection details, | ||
| forwards endpoints to your host, and runs one-off actions like topping up a | ||
| wallet. These are ad hoc commands, not desired state. | ||
|
|
||
| The reason for the boundary is that Kubernetes is an excellent model for desired | ||
| state and a poor model for one-off actions. "This network should run with | ||
| db-sync enabled" is a natural fit for a controller that reconciles toward it | ||
| forever. "Top this address up right now" is not: encoding a single funding | ||
| request as a resource mutation would be awkward and would leave stale objects | ||
| behind. So durable state lives in CRDs and the operator, and transient actions | ||
| live in the CLI. The CLI is a Kubernetes client; it holds no privileged state of | ||
| its own and talks to the same API server any other client would. | ||
|
|
||
| ## CardanoNetwork: the primary resource | ||
|
|
||
| The primary custom resource is `CardanoNetwork` (API group `yacd.meigma.io`, | ||
| version `v1alpha1`). It is intentionally environment-shaped rather than | ||
| node-shaped: it describes a Cardano *network* and the chain-access services | ||
| exposed beside it, even though the first runtime reconciles a single primary | ||
| node. | ||
|
|
||
| A `CardanoNetwork` owns the core Cardano substrate. Its spec carries the network | ||
| `mode` (`local` or `public`), the primary `node` workload (shared by both | ||
| modes), the mode-specific `local` or `public` block, and a `chainAPI` block for | ||
| the services exposed next to the node. The controller reconciles the node into a | ||
| workload backed by a PVC for the node database, publishes resolved network | ||
| identity and cluster-local Service endpoints into status, and tracks health | ||
| through a set of `metav1.Condition` entries (`Ready`, `NodeReady`, | ||
| `NodeSynchronized`, `OgmiosReady`, `KupoReady`, `ArtifactsReady`, and others). | ||
| Status reports only what the controller can | ||
| observe in-cluster, so consumers do not trust stale or hand-edited values. | ||
|
|
||
| Two chain-access services are enabled by default, because a raw `cardano-node` | ||
| is not enough to build against. [Ogmios](https://ogmios.dev) is the default | ||
| chain-access API: it gives YACD and developers a JSON/RPC interface for query, | ||
| submit, and evaluate without every client having to share the node's Unix | ||
| socket. [Kupo](https://cardanosolutions.github.io/kupo/) is the default chain | ||
| index. Funding is CLI-native: every local network is created with a | ||
| genesis-funded `faucet` wallet, and the `yacd wallet` verbs build and submit | ||
| funding transactions directly over Ogmios and Kupo — there is no in-cluster | ||
| faucet service. For the exact defaults, ports, and images, see the | ||
| [CardanoNetwork reference](../reference/cardanonetwork.md). | ||
|
|
||
| ## Supporting-service CRDs: CardanoDBSync | ||
|
|
||
| Heavier services are modeled as **separate CRDs with separate controllers**, | ||
| rather than as ever-growing fields on `CardanoNetwork`. The first such service | ||
| is `CardanoDBSync`, which runs | ||
| [cardano-db-sync](https://github.com/IntersectMBO/cardano-db-sync) and its | ||
| Postgres database. | ||
|
|
||
| A supporting CRD references a same-namespace `CardanoNetwork` through its | ||
| `networkRef` and derives chain information from that network rather than | ||
| re-declaring it. This keeps each controller focused: the db-sync controller | ||
| reasons about db-sync, Postgres, and ledger state, while the network controller | ||
| reasons about the Cardano substrate. It also keeps ownership clean. Enabling | ||
| db-sync does not have to disturb the primary node, and the db-sync controller | ||
| owns its own workload, storage, database wiring, config, and status. | ||
|
|
||
| The default placement is a **dedicated follower node**. Rather than forcing the | ||
| primary node to share its Unix socket, the db-sync controller runs its own | ||
| follower `cardano-node` colocated with db-sync in one workload. The follower | ||
| joins the primary network over ordinary node-to-node TCP and exposes a local | ||
| socket that db-sync consumes inside the same Pod. This costs extra CPU, memory, | ||
| storage, and startup time, but it preserves controller isolation: the primary | ||
| node never restarts because you added an indexer. | ||
|
|
||
| A `primarySidecar` placement mode is the explicit exception. When the cost of a | ||
| duplicate node outweighs the benefit of isolation, db-sync can be composed | ||
| directly into the primary node Pod and consume the primary socket. That trades | ||
| isolation for a single node copy and rolls the primary workload when the | ||
| attachment changes; it is supported for local and non-mainnet public profiles, | ||
| but not for public mainnet. For the field-level details of both modes, see the | ||
| [CardanoDBSync reference](../reference/cardanodbsync.md) and the | ||
| [db-sync operator guide](../operator/db-sync.md). | ||
|
|
||
| ## Why a Unix socket forces this shape | ||
|
|
||
| Many Cardano tools want direct access to a node's Unix socket, and that single | ||
| fact drives much of the architecture. A Unix socket is a local-filesystem IPC | ||
| object. It is not a cluster-wide endpoint and cannot be exposed through a | ||
| Kubernetes Service. The robust pattern is to share it *within one Pod* using an | ||
| ephemeral volume: the node data directory lives on a PVC, while the socket | ||
| directory is ephemeral and mounted by sidecars in the same Pod. | ||
|
|
||
| This is why Ogmios runs as a sidecar that mounts the node socket and re-exposes | ||
| chain access as a network API, and why socket-hungry services like db-sync | ||
| default to a colocated follower node instead of reaching across Pods. YACD | ||
| deliberately avoids RWX PVCs and hostPath socket sharing, which are fragile, | ||
| scheduler-sensitive, and a poor fit for shared clusters. The same constraint | ||
| shows up at the CLI boundary: tools that speak a network protocol can be | ||
| port-forwarded to your host, but a tool that needs the node socket directly | ||
| (notably `cardano-cli`) must run *inside* the node Pod. | ||
|
|
||
| ## Local vs public networks | ||
|
|
||
| The two network modes differ mostly in where chain material comes from. | ||
|
|
||
| A **local** network is generated and owned by YACD. The controller produces | ||
| fresh genesis and node configuration in-cluster from the spec: network magic, | ||
| ledger era, slot and epoch timing, generated stake-pool topology, and a curated | ||
| genesis profile (for example a zero-fee preset for fast tests). The generated | ||
| artifacts are staged and served over HTTP from the node's PVC by a small | ||
| cardano-tools serve endpoint, alongside a `manifest.json`, so the node and any | ||
| follower nodes consume the same configuration from one source of truth. The | ||
| `ArtifactsReady` condition reports when that bundle is staged and being served. | ||
|
|
||
| A **public** network joins a known profile: `preprod`, `preview`, or `mainnet`. | ||
| Instead of generating genesis, the controller fetches the published | ||
| configuration for the profile. For most profiles the node can sync from genesis, | ||
| but mainnet is far too large to sync that way in a development setting. For | ||
| mainnet, the spec requires a [Mithril](https://mithril.network) bootstrap: an | ||
| init container uses a Mithril client to download and verify a Cardano database | ||
| snapshot, seeding the node's PVC so the node starts from a recent point instead | ||
| of from genesis. The same HTTP artifact-serving pattern applies, so resolved | ||
| configuration is reachable in-cluster. | ||
|
|
||
| !!! warning "Mainnet is gated" | ||
| A public mainnet `CardanoNetwork` requires a Mithril bootstrap, large | ||
| persistent storage, and an explicit opt-in (`--allow-mainnet` on the CLI). | ||
| It is not the default development path. See the | ||
| [CardanoNetwork reference](../reference/cardanonetwork.md) for the | ||
| bootstrap and storage fields. | ||
|
|
||
| ## Host access: bridging the cluster to your host | ||
|
|
||
| The services a `CardanoNetwork` exposes are **cluster-internal** Service URLs. | ||
| That is correct for in-cluster consumers and for supporting controllers, but a | ||
| developer's tests and tools usually run on the host. The CLI's host-access verbs | ||
| bridge that gap. | ||
|
|
||
| `run`, `connect`, and `exec` are the three ways across the boundary. `run` wraps | ||
| a single command (the primary CI path), exposing host-usable Ogmios/Kupo URLs | ||
| through a small `YACD_*` environment contract so tests read ordinary environment | ||
| variables instead of parsing a YACD file. `connect` holds supervised | ||
| port-forwards open in one terminal while you work in another, writing the URLs to | ||
| an `endpoints.json` file instead of wiring an environment. | ||
|
|
||
| `run` and the wallet funding commands do not always port-forward. A | ||
| CardanoNetwork can advertise a directly reachable `externalURL` for Ogmios and | ||
| Kupo — `yacd devnet` pins one on `localhost` (a NodePort mapped to a host port), | ||
| and a platform team can front a shared cluster with an ingress and declare its | ||
| URL once. The CLI probes that URL and uses it when reachable, falling back to an | ||
| ephemeral port-forward otherwise; `connect` always forwards. Port-forwarding | ||
| stays the universal transport because it assumes nothing about how the cluster is | ||
| reached. | ||
|
|
||
| `exec` is different, and it exists because of the socket constraint above. Tools | ||
| that speak a network protocol can be forwarded, but `cardano-cli` reaches the | ||
| node over its local Unix socket, which cannot be forwarded as a TCP port. So | ||
| `exec` runs the command *inside* the primary node Pod, where | ||
| `CARDANO_NODE_SOCKET_PATH` and the `YACD_*` variables are already set. The rule | ||
| is simple: forward network APIs to the host with `run`/`connect`; run | ||
| socket-bound tools in-pod with `exec`. For the verb behaviors, the `YACD_*` | ||
| table, and the `endpoints.json` schema, see the | ||
| [CLI reference](../reference/cli.md) and the | ||
| [connecting tools guide](../developer/connecting-tools.md). | ||
|
|
||
| !!! note "Funding is CLI-native" | ||
| Local networks are created with a genesis-funded `faucet` wallet. The `yacd | ||
| wallet` verbs spend from it by building and signing transactions on the host | ||
| and submitting them over Ogmios and Kupo; wallet keys live in labeled | ||
| Kubernetes Secrets and never leave for a server-side signer. See the | ||
| [funding guide](../developer/funding.md) and the | ||
| [security model](security.md). | ||
|
|
||
| ## Developer and operator workflows | ||
|
|
||
| The same system serves two overlapping audiences. | ||
|
|
||
| A **developer** typically authors one developer-facing config file, checks it | ||
| into a repository, and drives it with the CLI: render and apply the network, | ||
| wait for readiness, fund a wallet, and point tests at the forwarded endpoints. | ||
| They rarely hand-author CRDs; the CLI compiles their single config into the | ||
| decomposed Kubernetes resources the operator expects, giving them one pane of | ||
| glass while preserving clean resource boundaries in the cluster. | ||
|
|
||
| An **operator** installs and runs the YACD operator in a cluster (locally on | ||
| [k3d](https://k3d.io) or in a shared cluster), manages the manager Deployment | ||
| and its RBAC through the Helm chart, and reasons directly about CRDs, status | ||
| conditions, placement modes, and capacity for heavier services like db-sync. | ||
|
|
||
| The two workflows overlap because they act on the same resources through the | ||
| same API server. A developer's `yacd up` produces the very `CardanoNetwork` an | ||
| operator can inspect with `kubectl`; an operator's installed controller is what | ||
| makes a developer's config become a running network. The CLI is a convenience | ||
| layer over the cluster API, not a separate control plane. For the operator's | ||
| trust boundaries and the host-access trust gates, see the | ||
| [security model](security.md). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| # Security model | ||
|
|
||
| YACD is a development environment manager, and two facts shape its threat model: | ||
| wallet keys are real signing credentials, and the operator runs with cluster-wide | ||
| reach. This page explains the deliberate security decisions that follow: how | ||
| wallet keys are stored and signed with, what the cluster-scoped manager RBAC | ||
| means on a shared cluster, and how the chart's optional image-verification path | ||
| hardens the supply chain. For the concrete knobs, follow the links to the how-to | ||
| and reference pages. | ||
|
|
||
| ## Wallet key custody | ||
|
|
||
| Funding a wallet means signing a real transaction, so wallet keys are credentials | ||
| worth protecting. YACD keeps custody simple and Kubernetes-native. | ||
|
|
||
| - **Keys live in labeled Kubernetes Secrets.** Each managed wallet is a | ||
| `<network>-wallet-<name>` Secret in the network's namespace, holding the payment | ||
| signing key, verification key, and address. The genesis-funded `faucet` wallet | ||
| has the same shape (`<network>-wallet-faucet`) and is created and owned by the | ||
| operator. Storing keys as Secrets means they inherit Kubernetes RBAC, | ||
| encryption-at-rest, and backup rather than living in a bespoke store. | ||
| - **The CLI signs locally; the cluster never signs for you.** `yacd wallet` reads | ||
| the source wallet's signing key, builds and signs the funding transaction on | ||
| your machine, and submits it over Ogmios. There is no server-side signing | ||
| endpoint and no faucet HTTP service to authenticate against, so no long-lived | ||
| spending credential is exposed inside the cluster. | ||
| - **`export` writes keys deliberately.** `yacd wallet export` is the only path | ||
| that puts raw key material on local disk; it writes `0600` files under a `0700` | ||
| directory and never prints keys to stdout. | ||
|
|
||
| Because there is no in-cluster faucet service, there is no Bearer token to | ||
| distribute and no token-to-URL trust gate to reason about. Keys stay in Secrets, | ||
| and signing stays on the host. | ||
|
|
||
| ## Cluster-scoped manager RBAC | ||
|
|
||
| The chart binds the manager's ServiceAccount to a `ClusterRole`, not a namespaced | ||
| `Role`. The grant is broad by design: the operator manages `CardanoNetwork` and | ||
| `CardanoDBSync` objects and reconciles them into the core Kubernetes workloads | ||
| they need. | ||
|
|
||
| The `ClusterRole` grants, across all namespaces: | ||
|
|
||
| - `configmaps` and `persistentvolumeclaims`: create, get, list, patch, update, | ||
| watch | ||
| - `pods`: get, list | ||
| - `secrets`: create, delete, get, list, patch, watch | ||
| - `services`: create, delete, get, list, patch, update, watch | ||
| - `deployments` (`apps`): create, get, list, patch, update, watch | ||
| - `cardanonetworks` and `cardanodbsyncs` (`yacd.meigma.io`): get, list, watch, | ||
| plus get, patch, update on their `/status` subresources | ||
|
|
||
| The implication for a **shared cluster** is direct: a YACD install can create, | ||
| read, and delete Secrets, Services, ConfigMaps, PVCs, and Deployments in any | ||
| namespace, because that is the scope the reconcilers operate at. The `secrets` | ||
| verbs include `create` and `delete` because wallet keys and other runtime | ||
| material are managed Secrets. Treat the operator as a cluster-wide actor when you | ||
| decide where to install it; an isolated development cluster is the intended home, | ||
| not a multi-tenant production cluster shared with untrusted workloads. | ||
|
|
||
| RBAC creation is on by default but optional (`rbac.create`), and the role and | ||
| binding names are templated so an operator can substitute externally managed | ||
| RBAC. See the [installation guide](../operator/installation.md) and the | ||
| [configuration reference](../reference/configuration.md) for those values. | ||
|
|
||
| ## Supply-chain image verification | ||
|
|
||
| The release workflow attests the manager image and the Helm chart with | ||
| GitHub-native (Sigstore keyless) attestations. The chart ships an **opt-in** | ||
| Kyverno `ClusterPolicy` that verifies the manager image attestation at Pod | ||
| admission time, closing the gap between "an image is signed somewhere" and "only | ||
| verified images run in this cluster". | ||
|
|
||
| The policy is **disabled by default** (`kyverno.imageVerification.enabled: | ||
| false`) because it depends on a running [Kyverno](https://kyverno.io) | ||
| installation. When you enable it, Kyverno intercepts pod admission and rejects | ||
| images that do not carry a valid keyless Sigstore attestation matching the | ||
| configured attestor. | ||
|
|
||
| The attestor defaults encode trust in the `meigma/yacd` release pipeline rather | ||
| than in a long-lived key: | ||
|
|
||
| - **issuer** `https://token.actions.githubusercontent.com` — the OIDC identity | ||
| must come from GitHub Actions. | ||
| - **subjectRegExp** restricts the signing workflow to `meigma/yacd`'s | ||
| `release.yml` running on a `v<semver>` tag, so a signature produced by any | ||
| other workflow or fork does not satisfy the policy. | ||
| - **Rekor** at `https://rekor.sigstore.dev` — the transparency log that records | ||
| the keyless signing event. | ||
| - **attestation** of type `https://slsa.dev/provenance/v1` with build type | ||
| `https://actions.github.io/buildtypes/workflow/v1` — the policy checks SLSA | ||
| provenance, not just a bare signature. | ||
|
|
||
| Because the signing identity is keyless and tied to a specific workflow on a | ||
| release tag, there is no signing key to leak or rotate; trust flows from the | ||
| GitHub OIDC identity and the public transparency log. The issuer, subject | ||
| pattern, Rekor URL, validation failure action, and the set of image references | ||
| the policy applies to are all configurable under `kyverno.imageVerification.*`. | ||
| To enable and tune verification, see the [installation guide](../operator/installation.md); | ||
| for the full value surface, see the [configuration reference](../reference/configuration.md). |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
actions/checkout is pinned to a mutable version tag (@v4). If this tag is reassigned to a malicious commit, arbitrary code could run in your workflow. Pin this action to a specific immutable commit SHA.