Nothing reaches into your cluster.
The connector dials out over HTTPS and holds the stream open. It applies Argo Workflows objects and refuses everything else. Runs can get their own namespace and service account, and both go when the run does.
Nothing reaches into your cluster
Every RPC is opened from inside your cluster. The connector dials out over HTTPS to one endpoint and holds the stream open. That is the whole of what reaches us. Nothing to open, nothing to allowlist.
- No inbound rule, nothing published outside the cluster, no VPN, no bastion, no allowlisted source range.
- On a stock install the only ports the connector listens on are health and metrics, both cluster-local. Two switches can add one each, and both ship off: the OTLP receiver, and the log forwarder that hosted log mode needs.
outbound -> HTTPS, one endpoint, opened by the connector
inbound -> noneIt applies workflow objects and refuses everything else
The connector will apply a Workflow, a WorkflowTemplate, a CronWorkflow or a ClusterWorkflowTemplate. Anything else is refused at decode time, before it reaches the API server.
- The allowlist runs inside your cluster, not in dagweave.
- Two layers have to agree: the allowlist, and RBAC. Cluster-wide, a stock install writes Argo Workflows objects and the connector's own CRDs, and reads pods, ConfigMaps and pod metrics in every namespace. Secrets there are get-only, so it can fetch the one it is handed the name of and never enumerate them. The log collector reads nodes and namespaces as well, to stamp a line with where it ran.
- In the namespace you install it into, the connector reads and writes Secrets, ConfigMaps and Leases, and creates Events. It uses them for its keypair and its leader-election lock, and the rules are not pinned to those objects, so the grant covers all of them in that namespace.
- Name your namespaces and most of that stops being cluster-wide. With one namespace listed, six resources move out of the ClusterRole and into a Role scoped to it: ConfigMaps, CronWorkflows, Pods, Secrets, Workflows and WorkflowTemplates. What stays cluster-wide is the connector's own two CRDs and nothing of yours.
allowed -> Workflow, WorkflowTemplate, CronWorkflow,
ClusterWorkflowTemplate
anything else:
"refusing to apply disallowed resource"The private key is generated in your cluster and stays there
At enrollment the connector generates an Ed25519 keypair from crypto/rand, inside your cluster, and writes it to a Secret in your namespace. dagweave is sent the public half, and stores that plus a SHA-256 hash of the one-time enrollment token.
- The chart ships no private key. The private half never leaves your namespace.
- One keypair per connector, generated the first time it starts. Nothing is shared between clusters or between customers.
- The enrollment token is spent on first use, and we only store its hash. Spent means we clear our hash of it, so a second presentation is refused. The token itself stays wherever you put it, in the Secret and in your Helm release, so treat it as spent rather than gone.
- A dispatch is only delivered on the stream the connector signed for. Nothing lets one connector drain another's work.
in your namespace, the enrollment Secret
private-key 64 bytes, ed25519, never leaves it
public-key 32 bytes, ed25519, sent once at enrollment
enrollment-token one-time. Stays on disk: nothing here removes it,
and your Helm release Secret keeps a copy too
at dagweave
the public key verifies the assertion on every request
the token sha256 hex of it, and nothing else
expires 24h after minting, bound or notWhat is on the wire
Every call after enrollment carries a short-lived assertion the connector signs with that private key. dagweave looks up the public key registered for that connector and verifies the signature before doing anything else.
- The assertion that dispatches work is worth one connection. dagweave records its jti and refuses a second presentation, and it expires after five minutes.
- The assertion that ships logs is not spent that way, because it sits in a push loop rather than opening one stream. A captured copy keeps working until it expires, so dagweave refuses any whose expiry sits more than fifteen minutes ahead, and it carries a different audience so it cannot be presented to the channel that dispatches work.
- The endpoint must be HTTPS. Only loopback addresses are exempt.
- The connector authenticates by signature. dagweave authenticates by TLS certificate, the way your browser does.
alg EdDSA (pinned by the verifier)
aud dagweave-connector (log push uses dagweave-log-ingest)
exp now + 5m (required, 30s leeway)
sub connector id (looked up, then verified)Logs and artifacts
The two take different paths. You choose where logs are stored. Artifacts stay in your object store. Downloading one goes straight from your bucket to your browser on a short-lived URL your connector signs, and those bytes never reach us. Viewing one in the browser is the exception, and it is deliberate.
- Pick your own S3 sink and your log lines never pass through dagweave; only a pointer to them does. The hosted sink is the one option that sends us the bytes, and it is not the default. Send them anywhere else and they are yours alone, because dagweave has no way to read those back.
- Artifact credentials are read from a Secret in your cluster. dagweave never holds a kubeconfig and never holds your object store keys.
- Viewing an artifact routes its bytes through dagweave, up to 20 MiB, because a presigned URL cannot carry the nosniff header and the Content-Security-Policy that make somebody else's HTML safe to open. We serve it from a separate registrable domain under a sandbox that cannot reach your account or your cluster. Downloading the same file does not go that way. Nothing is stored: the connector reads the object and keeps no copy, and neither do we.
logs -> your own S3 sink a pointer dagweave resolves
the hosted sink the one that sends us the bytes
console / Loki / HTTP they receive them; we cannot read them back
unset, the default nothing recorded for us to point at
artifacts -> stay in your object store
download your browser GETs it with a presigned URL, 1h default
view we fetch it and serve it sandboxed, 20 MiB capYou choose what it can reach
The chart writes a rule per feature, and leaving a feature off means its rule is never written. Artifact fetching reads a credential Secret, so it needs Secret reads. Leave artifacts off and that grant is never written.
- Rules for its own CRDs, leader election and metrics are not tied to any feature.
- submit, status and logs work the same way. Disable one and its rules are never created.
- The connector re-checks each feature in code, against the connection serving the command.
- The connector keeps itself up to date. There is no upgrade for you to schedule.
- Self-update can only rewrite the connector's own Deployment: the Role names that one object in resourceNames, and holds get and patch. The tag it moves to is not signature- or digest-checked and can go backwards, so pin the image yourself and turn selfUpdate off if that matters to you.
submit: workflows, workflowtemplates, cronworkflows (RW)
status: workflows, workflowtemplates, cronworkflows (R)
configmaps (R), pods (list), pod metrics (R)
logs: pods, namespaces, nodes (R), the log collector's own role
no pods/log: Vector tails the files off the node
artifacts: secrets, get by name, any namespace # on by default
artifactUploads shares this one rule # off
sources: secrets, list, release namespace only # off by default
runIdentity: serviceaccounts, roles, rolebindings # off
ephemeralNamespaces: namespaces, create and delete # off
clusterWorkflowTemplates: clusterworkflowtemplates (RW) # offNothing is left behind on the cluster
A run can get its own service account, or its own namespace. Either is created at dispatch. The namespace goes the moment the run reaches a terminal state; a run's service account is owned by the workflow object and is collected with it.
- No standing service account to audit, once run identity is on: the identity is minted per run and never reused. It ships off, because turning it on widens what the connector can do permanently rather than only during a run. Until you turn it on and write the permission templates, a run uses whatever service account its namespace already gives it, which is a standing one.
- A namespace boundary is the one thing that reliably stops a workflow reading another team's Secrets and volumes.
- Cleanup is one delete. Kubernetes takes the pods, the volumes and everything the run created with it.
- A run gets what it was scoped to, and nothing wider.
- The connector can only mint what a grant allows. There are no grants until you write them.
- If the connector is offline when a run ends, a sweep collects the namespace later. Cleanup does not depend on the run finishing cleanly.
dispatch -> namespace or service account created
run -> the workflow uses it, and nothing else can
finish -> namespace deleted, everything inside it with it
missed -> a sweep deletes what the run could notYour cluster runs every workflow
Your Argo Workflows controller executes the workflows. dagweave sends them there and reads status back. A running workflow needs nothing from us.
- Running workflows keep running. The Argo Workflows controller owns them, not us.
- Deployed CronWorkflows keep firing on schedule. The controller owns the timing.
- When the connector reconnects, dispatch and status resume. Queued work is durable, and the cluster's current status is re-reported.
a running workflow keeps running. Your Argo controller
owns it, and it is an object in your cluster
a deployed CronWorkflow keeps firing. Your controller owns the timing
the connector keeps retrying, on an exponential jittered
backoff that resets once a session is healthy
a run we had not sent stays queued here: accepted, recorded, and
deliberately not sent anywhere
when the stream is back the connector re-reports current status,
which heals whatever was missed while it was downConnect a cluster you already run.
dagweave is in early access. Leave an email and we will bring you on as we open it up.