09

Add Delegated User Access and Private Connectivity

Let a user approve one source action while the broker reaches the source through a route that is not public.

System map · Day 09

Whole-system design

Five stable layers. Today's work is expanded and linked; the rest stays in context.

Product and authority

Covered — HelixWorks control plane · Generated application plane

People and product entry points

Design target · not proved

Delegated human authority is a worked design for a separately revocable path; the current source implements authentication only.

Identity and policy

Design target · not proved

The required user, tenant, audience, source, operation, and expiry intersection is specified but not implemented in the repository.

Delivery and desired state

Covered — CI and immutable artifactsAhead — Git desired state · Argo CD reconciliation

Cloud and orchestration

Covered — Terraform and AWS APIs · Kubernetes or EKS control plane

Accounts, VPC, DNS, and private paths

Design target · not proved

Private DNS, transit, VPN or Direct Connect, and source firewall policy are design-only until matching infrastructure exists.

Compute and traffic

Covered — Worker compute · Platform service workloads · Generated app workloadsAhead — Ambient mesh data plane

Storage and evidence

Covered — Evidence and observabilityAhead — Infrastructure state · Cluster desired and live state

Product data and artifacts

Design target · not proved

Delegated-grant revocation metadata is designed here, while the current source provides no durable delegated-grant store.

The enterprise problem and today’s slice

Enterprise problem: A private IP address does not decide whether a person may read a document, and a user token does not make a private route. Treating either as both creates a reachable source with the wrong authority.

Whole-course context: The platform already has an isolated preview and a machine identity for scheduled work. This day adds the optional human-authority path and the network path that the Connector Broker uses when an enterprise source must stay private.

Today’s slice: Implement a delegated read through a broker running in Kubernetes: exchange the user’s short-lived authorization code, intersect it with the workload grant, resolve the source in private DNS, and send the call through a VPC-to-enterprise route. The source remains the final record-level authorizer.

End-of-day evidence: A trace proves an allowed delegated read used the private endpoint; a public-DNS probe, a revoked grant, and an out-of-scope request each fail for different reasons while a scheduled machine-only read remains healthy.

Still unsolved: Generated-app login and sharing roles arrive in later days. The AWS account, VPN/Direct Connect, Transit Gateway, and production EKS details are made concrete in the infrastructure days; this day defines the workload-to-source contract they must satisfy.

Customer outcome and implementation focus

An employee chooses Connect as me only when a request needs that employee’s source permissions; scheduled indexing continues to use the existing workload grant. The implementation decision is to put token exchange, grant state, scope intersection, private DNS lookup, and audit evidence in Connector Broker—not in the generated app and not in the browser. The observable success is a source response tagged with a broker trace; revocation must stop the delegated path before any source call.

PathCustomer-visible triggerDecisive result
Delegated readEmployee approves a documents.read requestBroker obtains a short-lived source token, uses the private route, and returns only allowed documents.
Scheduled readRefresh job runs without a personBroker uses its workload grant only; it never inherits a prior employee’s access.
Revoked or out-of-scope readEmployee has revoked consent or requests an excluded collectionBroker returns 403 before calling the source and records a denial trace.

Components in focus

Private connectivity is a chain of independent responsibilities. A failure at one link must not be reported as an authorization decision at another.

Component or layerOwnerResponsibility in this day
Generated application APIApplication teamStarts a delegated action and receives a broker result; it stores no source refresh token.
Connector Broker DeploymentConnector platformExchanges authorization codes, calculates effective scope, calls the source, and emits audit events. It runs as bounded pods on EKS worker-node CPU and memory.
Kubernetes ServiceAccount and workload identityRuntime platformGives Broker its own cloud/secret access; it is separate from the employee and from generated-app membership.
Authorization serverEnterprise identity teamAuthenticates the employee and issues a short-lived authorization code/token with consented scopes.
Grant databaseConnector platformPostgreSQL stores encrypted grant metadata, subject, source tenant, scope set, expiry, and revocation version. It is authoritative for broker-side consent state.
Token cacheConnector platformRedis caches only short-lived exchanged access tokens keyed by grant version; a revocation deletes this cache key. Redis is not the authority.
Secrets managerRuntime platformHolds the OAuth client secret and encryption key reference; pods fetch them through workload identity.
Private DNS and resolverNetwork platformResolves source.corp.example to a private address only from approved VPC subnets.
VPN/Direct Connect, Transit Gateway, route table, firewallNetwork platform and enterprise network teamCarries packets from EKS private subnets to the enterprise source and permits only Broker-to-source TCP 443.
Enterprise source APISource ownerApplies its own tenant and record-level policy after transport succeeds.
Evidence storeAudit platformPersists immutable trace, DNS answer class, route identity, grant version, source decision, and denial reason.

Generated-application database: Not involved in this slice — the generated app receives an opaque broker operation ID, while grant and evidence state stay with their respective owning services.

Build the delegated-grant boundary

The browser must never hand a reusable source token to the generated app. Instead, it receives a one-time authorization code after user consent and sends that code to Broker over the existing authenticated application session. Broker performs the OAuth authorization-code exchange server-side, encrypts the refresh material if the source requires it, and returns an opaque operation result.

The employee starts an application action; Broker is the only component that talks to the authorization server and source API. The source API is deliberately shown before any network detail: it is the final authority, not a database that Broker can bypass.

def effective_scope(workload: set[str], delegated: set[str]) -> set[str]:
    # A delegated request may use only permissions granted to both identities.
    return workload & delegated

if grant.revoked_at or grant.expires_at <= now():
    raise Forbidden("delegated consent is no longer active")
scope = effective_scope(workload_grant.scopes, grant.scopes)
if requested_scope not in scope:
    raise Forbidden("requested source scope is outside the intersection")

Connector Broker evaluates this intersection before creating a source request. It reads the grant row from PostgreSQL and either derives the effective scope or emits a denial; it can never turn the two grants into a union. On denial, the trace records the workload grant, delegated-grant version, requested scope, intersection result, and source_call=false, proving that no source connection was opened.

Give the broker a private path to the source

The private route begins before TLS: Broker’s pod asks cluster DNS for the source name, CoreDNS forwards the private zone to the VPC resolver, and the resolver returns a private address. Packets then leave the pod through the CNI, traverse the EKS private subnet route table, Transit Gateway, and VPN or Direct Connect, and reach the source firewall. The firewall allows only Broker’s security group or egress address on TCP 443. TLS still verifies the source certificate; private routing is not a substitute for encryption.

The first four boxes retain their earlier meaning. The added DNS, route, and firewall boxes explain how a permitted request reaches the same source without publishing it. A route success proves only transport; source authorization still follows.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: connector-broker-egress
  namespace: connectors
spec:
  podSelector:
    matchLabels:
      app: connector-broker
  policyTypes: [Egress]
  egress:
    - to:
        - ipBlock:
            cidr: 10.42.18.0/24 # Enterprise source private subnet, not 0.0.0.0/0.
      ports:
        - protocol: TCP
          port: 443

The cluster CNI enforces this rule for Broker pods, while VPC routes and the enterprise firewall enforce the next boundaries. Allowed packets traverse the pod interface, node ENI, private subnet route, and private link; the policy does not create a VPN, DNS zone, or firewall rule. Verify the path with a flow log for Broker-to-10.42.18.0/24:443, then prove the boundary by showing that a public-IP request or a request to another subnet is denied.

Persist revocation without persisting authority in the app

Revocation needs durable state because a cached token can otherwise outlive the user’s decision. Broker stores grant metadata and an encrypted secret reference in PostgreSQL, increments a revocation_version atomically, and deletes the matching Redis token-cache key. Source tokens are never copied into generated-app tables or browser storage.

RecordAuthoritative owner and storeConstraint and lifecycle
delegated_connector_grantConnector Broker, PostgreSQLdelegated_grant_id is scoped by organization_id, source tenant, subject reference, and workload grant reference. Active grants require a future expiry; revoke increments revocation_version, deletes encrypted secret material, and retains minimal audit metadata.
delegated_token_cacheConnector Broker, RedisKey is grant ID plus revocation version. TTL is shorter than the source token lifetime; eviction is safe because Broker can exchange again, but no cache entry may survive a version change.
private_source_evidenceAudit platform, append-only evidence storeTrace ID joins grant version, DNS answer class, route label, source request ID, decision, environment, and timestamp. Retention follows audit policy; source content is excluded.

PostgreSQL is the durable consent authority, Redis is disposable acceleration, and the evidence store proves which decision and path occurred. Adding a cache never changes source permission; it only avoids repeating a valid token exchange.

Run the proof loop

An implementation is incomplete if it can show only an allowed request. Run the following against a disposable connector and a test source collection; record the exact grant, source, environment, and trace IDs.

kubectl -n connectors exec deploy/connector-broker -- \
  getent hosts source.corp.example # The private answer must be in 10.42.18.0/24.

kubectl -n connectors exec deploy/connector-broker -- \
  curl --fail --resolve source.corp.example:443:10.42.18.20 https://source.corp.example/health
# Confirms TLS plus the private path from the Broker pod.

kubectl -n connectors exec deploy/connector-broker -- \
  curl --connect-timeout 3 https://198.51.100.20/health
# Expected failure: public source address is not reachable from this workload.
ProbeExpected observationWhat it proves
Active consent, matching workload scope, private DNS200 plus source request ID and trace IDAll authority and routing links worked for one bounded resource.
Revoke consent, then repeat the same request403, grant version changed, source_call=falseBroker enforced revocation before transport or source work.
Valid private route with excluded source scope403 from Broker or source policyReachability did not become permission.
Public DNS/address probeResolution or connection failure; no source public flow logThe source was not silently made public.
Scheduled workload-only refresh200 with no delegated grant IDMachine work remains independent of human authority.

Before and after, side by side

A direct application-to-source call either exposes the source or leaves consent and route evidence scattered across layers. The new design makes Broker the single place that intersects authority while the network team owns the route.

Key takeaways

  • Delegated authority, workload authority, and private reachability are three independent controls; neither one implies either of the others.
  • Broker owns token exchange, scope intersection, revocation, and evidence; PostgreSQL is durable authority and Redis is only a short-lived cache.
  • Private DNS, routes, and firewalls carry packets to the source, while source policy still decides the record-level request.
  • Prove the design with separate allowed, revoked, out-of-scope, public-path-denied, and workload-only checks.

Checklist

  • [ ] Broker, not the browser or generated app, performs the authorization-code exchange.
  • [ ] Effective scope is an intersection of current workload and delegated grants.
  • [ ] Private DNS, route, firewall, TLS, and source policy each have independent evidence.
  • [ ] Revocation deletes cache state and prevents the next source call.