atmos plugin
Extend Atmos with independently compiled Go authentication providers, credential-exchange identities, and commands. The OIDC starter demonstrates browser login, token verification, an AWS credential exchange, cached reuse, refresh, and logout.
Declare packages, discovery registries, and install policy in your atmos.yaml.
Build, test, publish, and run your local build through a profile without reinstalling.
Usage
atmos plugin <command>
Create a working OIDC plugin
Generate source using Atmos's existing scaffolding system. Start in any working directory; no demo configuration, copied fixtures, or registry is needed for these local authoring checks:
atmos plugin create ./corporate-oidc --kind=acme/oidc --module=example.com/acme/oidc --defaults
cd corporate-oidc
atmos build
atmos test
These custom commands are defined in the generated atmos.yaml: build compiles
the plugin, and test validates its capabilities and runs the conformance suite.
The direct atmos plugin build, validate, and test commands remain available.
The generated README describes the issuer registration and broker contract. For development against an unreleased Atmos checkout, pass --sdk-path /absolute/path/to/atmos/sdk to plugin create. For released builds, the Plugin SDK version follows the Atmos release; --sdk-version can select a specific compatible version. Only plugin authors need Go.
Build progress appears in an output viewport, followed by the executable path.
Validation shows a capability table; testing explains each completed check and
summarizes its results. Use --format json with validate or test when consuming
the results programmatically.
The default conformance suite uses local mock endpoints, an isolated keyring, and a browser fixture that follows a real loopback callback. It checks fresh noninteractive login, S256 PKCE login, validated token envelopes, broker exchange, cached reuse across plugin processes, refresh rotation, logout, and command output. The repository's CLI acceptance test also runs the flow across separate Atmos processes with an encrypted file keyring. These checks do not establish compatibility with a customer's live issuer.
The plugin.tests.yaml file declares command fixtures, the starter OIDC fixture, and authored Atmos test steps. Live integration steps are separate and run only with --integration; an empty live suite is an error.
Try the generated local registry
With Docker running, the scaffold can publish to its own anonymous OCI emulator:
atmos dev registry up
atmos push
atmos plugin search oidc --registry=local
atmos plugin install local/acme/oidc --as=corporate --version=0.1.0
atmos plugin list
atmos plugin info corporate
atmos dev registry down
The default port is 15557; edit stacks/local.yaml and the matching references
in atmos.yaml to change it. No login is required. Stopping this ephemeral
registry discards its artifacts. push builds and tests before publishing,
leaves discovery to the configured repository index, and preserves each OCI layout under the gitignored
.atmos/release/ directory.
For remote publishing, configure a registry identity, run
atmos auth login --identity publisher, and use
atmos push --to oci://registry.example.com/team/oidc.
Release publication never modifies a shared catalog. Run search with --refresh to
discover a newly published release immediately.
Add --sign --yes to sign the release; registry login and release signing use
separate identities. The generated README includes the configuration.
Package identity and source metadata
The plugin manifest uses the shared Atmos envelope. metadata.name is the stable,
organization-qualified package ID; display_name is an optional human-readable
name. Authors can configure the description, version, maintainer, and source
repository:
apiVersion: atmos/v1
kind: AtmosPluginConfig
metadata:
name: acme/oidc
display_name: Corporate Identity
description: OIDC login and AWS credential exchange
version: 1.2.3
author: Infrastructure team
repository: https://github.com/acme/atmos-oidc
Packing preserves these fields in every platform payload. plugin info exposes
the installed manifest. The repository URL is supplied by the author; the
consumer lockfile separately records the OCI source and verified release digests.
Configure a consumer
Add provider and identity settings to the consumer project's atmos.yaml:
auth:
providers:
corporate:
kind: acme/oidc
spec:
issuer: https://login.example.com
client_id: native-application-client-id
scopes: [openid, profile, offline_access]
identities:
broker:
kind: acme/oidc-broker
via:
provider: corporate
principal:
endpoint: https://broker.example.com/credentials
deploy:
kind: aws/assume-role
via:
identity: broker
principal:
assume_role: arn:aws:iam::123456789012:role/deploy
Register a public native application with the issuer, enable authorization code with S256 PKCE, and allow an ephemeral loopback port at http://127.0.0.1:<port>/callback. The starter validates ID-token signature, issuer, audience, expiry, nonce, authorized party, and access-token hash when present. Its broker receives the access token and returns temporary AWS credentials. Refresh tokens remain in private session storage.
To run your local build in the consumer, point the package at the plugin directory with path in a
profile of your own. Any profile name works; this one is my-local-build:
plugins:
packages:
corporate:
path: /absolute/path/to/corporate-oidc
A relative path is relative to the project directory. From the consumer directory, with the profile active:
export ATMOS_PROFILE=my-local-build
atmos plugin run corporate --
atmos auth login --identity=broker
atmos auth login --identity=deploy
atmos auth logout --provider corporate --keychain --force
A package with path runs the directory in place: it is not fetched, locked, or checked for a signature, and Atmos
prints a warning when it starts the plugin. Rebuild with atmos plugin build and the next command runs the new
executable. Without the profile, the same project uses the registry release. See the
plugin development tutorial for the full loop, --frozen behavior, and policy for
local builds.
The provider uses the consumer's configured auth keyring. Session scopes include the package, auth configuration, and realm. Atmos serializes auth and logout through credential-file publication. Logout reports remote revocation failures while removing local plugin sessions and dependent credentials; it does not promise to end the issuer's browser session.
Native plugins execute with the current user's operating-system privileges. The private API limits access to Atmos-managed services; it is not an operating-system sandbox.
Publish and lock a release
Compile every platform declared in plugin.yaml before packaging. Rebuild whenever the embedded manifest changes.
atmos plugin pack ./corporate-oidc --output=./oidc-release
atmos plugin publish ./oidc-release --to=oci://registry.example.com/team/oidc
atmos plugin install oci://registry.example.com/team/oidc --as=corporate --version='^0.1'
atmos plugin install --frozen
Commit the package declaration and .atmos.plugins.lock.yaml. A profile that sets path stays personal and is not part of the release. The lock records the selected version, OCI index and platform digests, exported capabilities, and verification status. Digest verification establishes content integrity. Optional Sigstore verification also authenticates the publisher of the immutable release index. The lock records the verified certificate identity and issuer for inspection; Atmos rechecks signature evidence against the current policy before executing the plugin.
An OCI image layout can be copied between environments and installed with --from-layout. Packaging and installation reuse Atmos's shared OCI transport and archive verification.
The version you pass to install is stored verbatim. Without --version, a new declaration is
written as a caret pin of the release that was installed (^1.4.2, or ^0.1.0 for a 0.x release,
which allows only that minor; a pre-release is pinned exactly). See plugin install.
Declare packages
Declare a package in plugins.packages and use it; a separate install command is optional:
plugins:
packages:
corporate:
source: oci://registry.example.com/team/oidc
version: "^0.1.0"
timeout: 5m
plugins.packages.<alias>.source- The release repository, as an
oci://reference. Required while the package is enabled. plugins.packages.<alias>.version- Semantic version constraint. Required while the package is enabled. An exact version pins; a range lets
plugin updateadvance within it. plugins.packages.<alias>.path- A directory that holds a locally built plugin (
plugin.yamland the executable it names). When set, the package runs from that directory instead of the release: no lock entry, cache, signature, or version check applies, andsourceandversionstay valid but unused. A relative path is relative to the project directory. Set it in a profile so it applies only while that profile is active. See the plugin development tutorial. plugins.packages.<alias>.enabled- Set to
falseto keep a declaration without installing or activating it. A disabled package is skipped, with a notice, byplugin installand by automatic installation, and it does not count as drift forinstall --frozen. Source and version are not required while it is disabled. plugins.packages.<alias>.timeout- A positive Go duration, such as
30sor5m, that bounds every call Atmos makes into the plugin (authentication, credential exchange, and commands). Omit it for no deadline. An invalid value fails configuration validation and names the package. plugins.packages.<alias>.command- Selects which command export
plugin runstarts when a package exports more than one. plugins.packages.<alias>.config- Settings passed to the plugin command. When the command declares a schema, the settings are validated against it.
plugins.packages.<alias>.scope- Only
projectis supported. Other values fail validation.
Automatic installation
Declared project plugins install automatically, but only when a command needs one. Commands such as
describe, list, or validate install nothing unless the configuration references an auth
provider or identity kind that Atmos does not provide itself. The lock records the exports of every
package, so Atmos chooses the package that exports the referenced kind without a network request.
Kinds that no locked package covers fall back to the declared packages that have no lock entry yet.
For each package that is needed:
- Locked and cached: nothing happens, and no registry is contacted.
- Locked, but the cache is empty: the exact locked digest is restored. This is allowed in CI, because the lock makes the result reproducible.
- Not locked yet: Atmos installs the package and writes the lock entry. CI refuses this with an
error that asks you to run
atmos plugin installlocally and commit.atmos.plugins.lock.yaml, so a pipeline never creates an unreviewed lock. - Declaration differs from the lock: the command fails and asks for an explicit
plugin installorplugin update. Normal execution never upgrades a locked selection.
Every automatic installation prints one notice that names the package, source, version, and digest, and states the signature status. An unsigned release produces a warning that asks you to review and commit the lockfile.
Disabled packages and packages that set path skip automatic release installation.
plugin run <alias> restores only the requested alias. plugin list, plugin info,
and lifecycle commands do not trigger automatic installation. With
plugins.policy.offline: true, a missing payload fails until supplied through an
explicit offline layout install.
Recovery and cancellation
Corrupt cache. Before a cached release runs, Atmos re-verifies its files against the locked digests. If a file was changed, added, or removed after verification, the release is treated as corrupt:
atmos plugin installand, outside CI and offline mode, automatic installation move the damaged directory aside to<release>.corrupt-<timestamp>, print a warning with its path, and download the release again by its locked digest. The damaged bytes are kept for inspection, never deleted.atmos plugin install --frozennever changes the cache, so it fails with an error that names the alias and the cache path. In CI, and withplugins.policy.offline: true, automatic installation fails the same way and leaves the cache untouched. The error's hint gives the repair command: runatmos plugin installwhile online, or with--from-layout.
Interrupted writes. Lifecycle commands (install, update, remove, registry add, and
registry remove) change atmos.yaml and .atmos.plugins.lock.yaml together. Atmos records the
complete change in .atmos/plugins/transaction.json first and then applies it, so you never see only
one of the two files updated. If a process dies in between:
- The next lifecycle command finishes the recorded change before doing anything else.
- Ordinary commands stop with an error that names the journal and says what resolves it. A journal that
matches the current files is finished by running
atmos plugin install. A journal that is not valid JSON is moved aside totransaction.json.corrupt-<timestamp>byatmos plugin install, which then continues. - A journal that conflicts with edits you made outside Atmos is never discarded automatically. Review it, then remove it or restore the named files to the state it records.
Cancellation. Pressing Ctrl+C during an installation leaves the declaration and the lock exactly as they were; verified downloads stay in the shared cache for the retry. A running plugin process is stopped with a termination signal, escalated to a forced kill after three seconds, and the whole process group is stopped on Unix, so no plugin process is left behind. A transaction that was already journaled is completed even if you cancel.
Require trusted publishers
Sign a release during publication, or sign an existing release by its immutable index digest:
atmos plugin publish ./oidc-release --to=oci://registry.example.com/team/oidc --sign
atmos plugin sign oci://registry.example.com/team/oidc@sha256:<index-digest>
The signing command uses a pinned, toolchain-managed Cosign release for keyless signing and attaches a standard Sigstore bundle to the OCI release. See plugin sign for interactive login and CI setup. Verification runs inside Atmos and does not require Cosign on consumer machines.
Configure trusted release publishers in the consumer's atmos.yaml:
plugins:
policy:
require_signature: true
trusted_publishers:
- publisher: acme
identity: "https://github.com/acme/atmos-oidc/.github/workflows/release.yaml@refs/tags/*"
issuer: "https://token.actions.githubusercontent.com"
packages:
corporate:
source: oci://registry.example.com/team/oidc
version: "^1.2.0"
publisher matches the namespace before / in metadata.name, such as acme
in acme/oidc. identity matches the certificate's complete subject alternative
name; * is the only wildcard. issuer is an exact match. These identify the
release signer, independently of the OIDC provider that the plugin implements.
A nonempty trusted_publishers list enables enforcement even when
require_signature is omitted. require_signature: true needs at least one
trusted publisher.
Atmos verifies the certificate chain, signed certificate timestamp, transparency
log inclusion and authenticated signing time, plus the release's SHA-256 index
digest. Trust comes from the public Sigstore trust root, updated using TUF;
plugins cannot supply their own trusted roots. Modern Sigstore bundle attachments
are supported, including Cosign's DSSE bundles. Legacy Cosign .sig attachments
and custom private trust roots are not supported by this configuration.
Security requirements from all loaded Atmos configuration files are retained:
require_signature and offline cannot be turned off by a later file. Each publisher list permits
any of its entries, but the same signature must satisfy every layer's list.
Signature policy governs released packages. A package that sets path has no signature, so use
plugin policy rules over origin: development and input.path to control local builds.
Verified bundles are cached beside the shared release payload. Atmos revalidates
them against the current policy on reuse; an edited lockfile's verified label
cannot grant trust. Online installation can fetch newly attached signatures when
the cached signer no longer satisfies policy.
For offline installation, copy a standard bundle as signature.sigstore.json
into the OCI layout directory, or supply multiple bundles in a signatures.json
JSON array. Set plugins.policy.offline: true and use --from-layout. The machine
also needs previously downloaded, unexpired Sigstore TUF metadata under the shared
XDG data directory (atmos/sigstore/tuf). Missing or expired trust metadata,
missing signatures, and untrusted signers fail closed without network access.
Restrict registries and plugins
Use plugin policies in the shared CEL/Starlark policy engine to require your company's registry and approve specific packages. The library includes approved registries and approved packages, each with both language variants and shared test cases.
These on: plugin rules load from protected policy.d/ or ATMOS_POLICY_DIR. They check registry
destinations before access and statically inspected packages before installation or activation,
including automatic installation and reuse from the shared cache. Registry rules also cover
discovery catalogs. The registry library policy denies local development (a path package or plugin test) and explicit --from-layout
imports unless the governing policy makes an exception. Offline layouts can claim an approved
repository without contacting it; the origin field distinguishes them from registry downloads and
cached reuse. Protect the project lockfile that selects cached releases.
Registry and package rules supplement plugins.policy signature verification: passing a CEL or
Starlark rule cannot weaken trusted publisher requirements. See
enforcement guidance for protecting the governing directory and runtime.
Parallel installation and shared storage
Missing packages download in parallel using the same progress renderer as
Atmos toolchain installation. The default limit is four; configure
plugins.max_concurrency to change it or set it to 1 for sequential installs.
Progress stays on stderr, leaving command data on stdout. Cached releases are
reused. Failed or canceled batches leave the project's previous declaration and
lockfile intact; successfully verified downloads remain available in shared storage.
plugins:
max_concurrency: 4
packages:
corporate:
source: oci://registry.example.com/team/oidc
version: "^1.2.0"
Downloaded releases use Atmos's shared XDG data directory, normally
~/.local/share/atmos/plugins/releases/<digest>/<os>/<arch>/. Projects selecting
the same release digest and platform share the verified payload. Each project
retains its own declarations and .atmos.plugins.lock.yaml. The standard
ATMOS_XDG_DATA_HOME and XDG_DATA_HOME overrides apply; plugins.cache_dir
selects an explicit storage root.
An exact version such as "1.2.3" pins selection. A range such as "^1.2.0"
allows plugin update corporate to choose the latest matching stable release.
Prereleases require an explicit prerelease constraint. Normal execution restores
the locked digest without upgrading. Use plugin update corporate --version "^2.0.0" to change the constraint deliberately.
Discover repositories and optional catalogs
For a small private collection, list the OCI repositories directly. Atmos builds and caches a local discovery index; plugin authors only need to publish their releases. Public or curated collections can publish catalog snapshots. Multiple sources can coexist, including a catalog per product:
plugins:
max_concurrency: 4
discovery:
cache_ttl: 15m
registries:
team:
host: registry.example.com
identity: registry-reader
repositories:
- oci://registry.example.com/team/oidc
- oci://registry.example.com/team/credentials
infrastructure:
catalog: oci://ghcr.io/acme/infrastructure/catalog:stable
developer-tools:
catalog: oci://ghcr.io/acme/developer-tools/catalog:stable
Both inputs feed the same search and alias-qualified installation:
atmos plugin search oidc
atmos plugin search --registry=team --refresh
atmos plugin install team/acme/oidc --as=corporate --version="^1.0"
Repository discovery fetches tags and static metadata, reusing unchanged release digests; it never downloads executables or runs plugins. Repositories must be listed explicitly. Freshness defaults to 15m; 0 always revalidates online, and --refresh bypasses the TTL. Offline policy permits an existing cache even when stale. Source policy is rechecked on cache hits, and cached discovery never substitutes for installation verification. See plugin search for cache locations and failure behavior.
Catalog maintenance is optional and separate from release publication:
atmos plugin registry add infrastructure oci://ghcr.io/acme/infrastructure/catalog:stable
atmos plugin catalog add infrastructure oci://ghcr.io/acme/infrastructure/oidc:1.0.0
Each catalog needs one externally serialized maintainer workflow. Its lock must cover reading current inputs, updating the snapshot, and publishing it. Revision and digest rechecks detect observed conflicts, but OCI tag updates do not provide universal atomic compare-and-swap. Independent release jobs should not all modify the same catalog. Product catalogs may have independent publishers.
Registry authentication
Registry authentication belongs to atmos auth. A plugin registry references an identity by name. Its host can be configured without a catalog or repository list when only authentication is needed:
auth:
identities:
registry-reader:
kind: oci/user
spec:
registry: ghcr.io
username: company-bot
password_env: GHCR_TOKEN
plugins:
registries:
corporate:
host: ghcr.io
identity: registry-reader
repositories:
- oci://ghcr.io/company/atmos-plugins/oidc
The oci/user identity obtains the password or personal access token from the named environment
variable. Omit password_env to enter the secret interactively at login. An explicitly configured
environment variable must contain a value; a missing value does not trigger another credential source. Credentials
use the existing realm-scoped auth keyring. Secrets are never written into the registry declaration
or plugin lockfile.
atmos auth login --identity=registry-reader
atmos plugin search oidc --registry=corporate
atmos auth logout --identity=registry-reader --keychain --force
Login can also happen lazily when a plugin operation needs that registry. Cached credentials are
reused, and expired credentials are renewed through the identity. atmos plugin registry login corporate
and logout corporate remain conveniences for the same auth lifecycle. A registry's binding applies
to discovery, package downloads, publishing, catalog writes, and signatures on that exact host.
For ECR or ACR, reference an existing cloud identity and configure its matching registry integration:
auth:
# Configure registry-reader as your existing AWS SSO, role, or OIDC-backed identity.
integrations:
corporate-ecr:
kind: aws/ecr
via:
identity: registry-reader
spec:
registry:
account_id: "123456789012"
region: us-east-1
plugins:
registries:
corporate:
host: 123456789012.dkr.ecr.us-east-1.amazonaws.com
identity: registry-reader
Atmos uses the integration for the actual destination host. Credentials are not forwarded to other hosts referenced by a catalog. Configure those hosts separately when they require an identity. Conflicting identities for the same host are rejected. A configured identity's failure stops the operation; it does not fall back to Docker or ambient credentials.
Registry authentication must be available before downloading its auth plugin. Use a built-in identity
or an auth plugin that is already available; a plugin cannot download itself using its own provider.
Registries without an identity use the shared credential chain described in
credential precedence: Docker configuration first,
then GHCR settings for ghcr.io, then credentials stored with plugin registry login.
Extension boundaries
The first implementation supports native Go auth.provider, auth.identity, and command exports. The shared API is named pkg/api; the standalone Plugin SDK is the separate module used by plugin authors.
A new Atmos primitive needs an extension descriptor, static validation, a versioned service contract, an adapter to its existing registry, and applicable contract tests. Installation and OCI transport operate on descriptors and package metadata. Starlark, Wasm, and adapters for other Atmos primitives remain future work. Atmos artifacts are not part of this implementation.
Commands
Compile a native Go plugin into the executable path declared in its manifest.
Add a release’s verified metadata to a catalog.
Build catalog metadata from a verified plugin release layout.
Publish a catalog with revision and digest conflict checks.
Unlist a package or version without deleting release artifacts.
Scaffold a Go OIDC provider, AWS broker identity, and command using Atmos scaffolding.
Delete a remote release by its immutable digest.
Inspect a selected plugin’s source, digests, exports, and local path.
Install a declared release or restore locked plugin releases. Local builds run through plugins.packages.<alias>.path instead.
List package selections and local path packages without network access.
Pack precompiled native executables and schemas into a multi-platform OCI layout.
Publish a verified release layout to an OCI registry.
Configure a registry host, repository discovery, or an optional catalog.
List configured registry hosts, discovery sources, and auth identities.
Authenticate a registry through its configured Atmos identity.
Log out the Atmos identity used by a plugin registry.
Remove a registry configuration while retaining installed packages and stored credentials.
Remove a plugin declaration and its lock entry.
Execute an installed plugin’s selected command and preserve its exit code.
Search cached plugin metadata from OCI repositories and optional catalogs.
Sign an immutable plugin release with Sigstore and publish its signature to OCI.
Test a prebuilt plugin using explicit fixtures and the shared Atmos test runner.
Explicitly advance selected release versions and update their lock entries.
Validate package metadata, supported capabilities, and local JSON schemas without executing plugin code.