Skip to content

RFC0055 Identity-Aware Routing#3758

Open
rkoster wants to merge 17 commits into
mainfrom
enhance-add-access-rule-ux
Open

RFC0055 Identity-Aware Routing#3758
rkoster wants to merge 17 commits into
mainfrom
enhance-add-access-rule-ux

Conversation

@rkoster

@rkoster rkoster commented Apr 10, 2026

Copy link
Copy Markdown

Summary

This PR implements the identity-aware routing RFC (RFC#1438) in the CF CLI. It provides the complete implementation from a prior POC, with two significant improvements:

  1. Terminology rebrand — updated to match the latest RFC revision ("route policies" instead of "access rules")
  2. User-friendly source flags — name-based flags (following add-network-policy conventions) instead of raw GUID selectors

Terminology Update (Breaking Change):

  • Commands: access-rulesroute-policies, add-access-ruleadd-route-policy, remove-access-ruleremove-route-policy
  • Flags: --enforce-access-rules--enforce-route-policies, --selector--source
  • API paths: /v3/access_rules/v3/route_policies
  • Resource types: AccessRuleRoutePolicy

This is an acceptable breaking change since the feature has not been GA-released.

Motivation

The original POC implementation used "access rules" terminology, but the RFC was updated to use "route policies" for better alignment with existing CF concepts. Additionally, the commands required users to manually construct GUID-based selectors (e.g., cf:app:d76446a1-f429-4444-8797-be2f78b75b08), which was cumbersome and error-prone.

This PR follows CF CLI conventions from commands like add-network-policy to provide a user-friendly interface with name-based flags.

Changes

1. Terminology Rebrand

Complete rebrand across all affected files:

  • Commands: route-policies, add-route-policy, remove-route-policy
  • Flags: --enforce-route-policies, --source, --source-app, --source-space, --source-org, --source-any
  • API endpoints: /v3/route_policies
  • Query parameters: sources, source_guids
  • Resource fields: RoutePolicy.Source, Domain.EnforceRoutePolicies, Domain.RoutePoliciesScope
  • Table columns: Updated to show "source" terminology

Rationale (per RFC commit be8d74c):

The term "route policy" is more idiomatic within Cloud Foundry, which already uses "network policies" for app-to-app network access. This naming convention better communicates that these are routing-layer policies that control which sources can access specific routes.

2. Command Interface Improvements

Before:

cf add-access-rule RULE_NAME DOMAIN SELECTOR --hostname HOST
cf remove-access-rule RULE_NAME DOMAIN --hostname HOST

After:

# add-route-policy examples
cf add-route-policy apps.identity --source-app frontend-app --hostname backend
cf add-route-policy apps.identity --source-space monitoring --hostname api
cf add-route-policy apps.identity --source-org platform --hostname shared-api
cf add-route-policy apps.identity --source-any --hostname public-api

# Cross-space/org access
cf add-route-policy apps.identity --source-app client --source-space prod-space --source-org prod-org --hostname api

# Advanced: raw source still supported
cf add-route-policy apps.identity --source cf:app:GUID --hostname backend

# remove-route-policy
cf remove-route-policy apps.identity --source cf:app:GUID --hostname backend

3. New Flags

add-route-policy:

  • --source-app APP_NAME - Specify source app by name (resolves to GUID)
  • --source-space SPACE_NAME - Specify space context for app lookup or create space-level policy
  • --source-org ORG_NAME - Specify org context for space/app lookup or create org-level policy
  • --source-any - Allow any authenticated app
  • --source SOURCE - Raw GUID-based source for advanced users

remove-route-policy:

  • --source SOURCE - Required. Specify the source to remove

create-shared-domain / create-private-domain:

  • --enforce-route-policies - Require route policies for all routes on domain
  • --route-policies-scope [app|space|org] - Minimum granularity for policies

4. RFC Alignment: No User-Provided Names

Per RFC commits 882b69a and 11752f2, route policies no longer have user-provided names. They are identified by their source only, with labels/annotations available for metadata.

Key Changes:

  • Removed RULE_NAME positional argument from add-route-policy command
  • Removed Name field from RoutePolicy API resource
  • Updated route-policies list command to show 4 columns: route, source, scope, source/name
    • The source/name column shows the resolved name of the app/space/org from the source
  • Updated remove-route-policy to use --source flag instead of name
  • Renamed DeleteAccessRule()DeleteRoutePolicyBySource()

5. Enhanced Output

add-route-policy:

Adding route policy for route backend.apps.identity as admin...
  scope: app, source name: frontend-app
  source: cf:app:d76446a1-f429-4444-8797-be2f78b75b08
OK

route-policies:

route                            source                                         scope   source/name
backend.apps.identity            cf:app:d76446a1-f429-4444-8797-be2f78b75b08    app     frontend-app
api.apps.identity/metrics        cf:space:2b26e210-1b48-4e60-8432-f24bc5927789  space   monitoring
public-api.apps.identity         cf:any                                         any

6. Validation & Error Handling

  • Validates exactly one primary source is specified
  • Provides helpful error messages when app not found in current space
  • Suggests using --source-space and --source-org flags for cross-space/org access
  • Follows CF CLI patterns from add-network-policy command

7. Code Quality

  • RoutePolicyArgs consolidates the positional-argument struct that was independently defined for add-route-policy and remove-route-policy into a single shared type in command/flag/arguments.go

Testing

New and updated test files:

  • command/v7/add_route_policy_command_test.go — flags, validation, error handling, success path
  • command/v7/remove_route_policy_command_test.go — flags, validation, error handling, success path
  • command/v7/route_policies_command_test.go — listing, column output
  • command/v7/route_policy_source_flags_test.go — 23 specs covering all source flag validation and resolution paths (internal package tests, no generated fakes needed)
  • command/v7/create_shared_domain_command_test.go — extended with --enforce-route-policies/--scope coverage, CAPI version check, shared behaviour helper
  • command/v7/create_private_domain_command_test.go — same coverage as shared domain
  • command/v7/enforce_route_policies_shared_test.go — shared Ginkgo behaviour helper for domain command tests
  • actor/v7action/route_policy_test.go — actor-layer tests
  • api/cloudcontroller/ccv3/route_policy_test.go — ccv3 client tests
  • actor/v7action/domain_test.go — extended with scope/enforce contexts using JustBeforeEach pattern
  • resources/route_policy_resource_test.go — marshal/unmarshal tests for the resource type

Coverage: flag validation, source resolution, GUID lookup, error handling, cross-space/org scenarios, CAPI version checks, domain flags.

Breaking Changes

This is a complete rebrand with no backward compatibility:

  • All command names changed
  • All API paths changed
  • All resource field names changed
  • All flags renamed

This is acceptable since:

  1. The RFC has not reached GA
  2. Only POC/lab implementations exist
  3. The RFC itself was updated with these breaking changes
  4. Clean cut-over is better than long deprecation for pre-GA features

Files Changed

  • ~40 files modified
  • Key areas:
    • Resources: route_policy_resource.go
    • API client: route_policy.go, api_routes.go, query.go
    • Actor: route_policy.go, error types, interfaces
    • Commands: route_policies_command.go, add_route_policy_command.go, remove_route_policy_command.go
    • Domain commands: Updated flags in create_shared_domain and create_private_domain
    • Generated fakes: fake_cloud_controller_client.go, fake_actor.go regenerated for new methods only — bulk Invocations() cleanup is in PR Regenerate counterfeiter fakes with v6.12.2 #3799

Checklist

  • Tests added/updated and passing
  • Documentation (help text) updated
  • Breaking changes documented
  • Follows CF CLI conventions
  • Aligned with RFC terminology rebrand
  • Code formatted with gofmt
  • Mocks regenerated with counterfeiter (bulk Invocations() cleanup in separate PR Regenerate counterfeiter fakes with v6.12.2 #3799)
  • CLI binary builds successfully
  • Manual testing against live CF deployment (requires CAPI backend support)

Pending

  1. Coordination with CAPI team to ensure API implementation uses new terminology
  2. Manual testing against a CF deployment with route policies API support
  3. Integration testing with full CF ecosystem

Related

Co-authored-by: Ruben Koster rkoster@starkandwayne.com

@rkoster

rkoster commented Apr 10, 2026

Copy link
Copy Markdown
Author

Don't worry about this PR just yet, just doing some more POC work on the RFC: cloudfoundry/community#1438

@rkoster rkoster changed the title Enhance add-access-rule command UX with intuitive name-based flags Implement identity-aware routing RFC with route policies terminology Apr 21, 2026
@rkoster rkoster force-pushed the enhance-add-access-rule-ux branch from a0ffff8 to 9409dbb Compare April 21, 2026 13:02
@rkoster rkoster changed the title Implement identity-aware routing RFC with route policies terminology RFC0055 Identity-Aware Routing Apr 22, 2026
@cppforlife

Copy link
Copy Markdown

I'll update the RFC with this terminology and push a new commit. Thanks for pushing on this—it's a significant UX improvement.

glad we all agree =)

one nit:
instead of

cf add-route-policy apps.identity --source-app frontend-app --hostname backend

i think it will be much more fluent to have

cf add-route-policy apps.identity --hostname backend --allow-from-app frontend-app 
cf add-route-policy apps.identity --hostname api --path /metrics --allow-from-space x
...
cf add-route-policy apps.identity --hostname api --path /metrics --allow-from cf:app:...

@ameowlia

Copy link
Copy Markdown
Member

Issue: add-route-policy and remove-route-policy do not have matching flags.

This works:

go run . add-route-policy apps.identity --source-app frontend --hostname backend

There is no matching –source-app for the remove policy.

go run . remove-route-policy apps.identity --source frontend --hostname backend -f
source must start with one of: cf:app:, cf:space:, cf:org:, or be exactly 'cf:any'
FAILED
exit status 1

@ameowlia

Copy link
Copy Markdown
Member

I was testing all of this and I accidentally deployed without updating capi, which resulted in this error message:

$cf create-shared-domain apps.identity --enforce-route-policies --scope any

Creating shared domain apps.identity as admin...
Unknown field(s): 'enforce_route_policies', 'route_policies_scope'
FAILED
exit status 1

Is there a way to give a clearer error message? Maybe checking the capi version? Or saying something like "Route Policies are not supported in this version of CAPI"?

@rkoster

rkoster commented Jun 17, 2026

Copy link
Copy Markdown
Author

@cppforlife — circling back on this since some time has passed.

i think it will be much more fluent to have --allow-from-app frontend-app / --allow-from-space x / --allow-from cf:app:...

The --source-* naming is intentional — it mirrors CF's existing network policy convention. cf add-network-policy uses SOURCE_APP as its primary concept:

cf add-network-policy SOURCE_APP DESTINATION_APP [-s DESTINATION_SPACE_NAME [-o DESTINATION_ORG_NAME]] [--protocol (tcp | udp) --port RANGE]

During RFC review, @ameowlia explicitly pushed for this feature to follow the network policy CLI pattern:

If you want to make it mirror c2c you should make these API endpoints that can be wrapped by the CLI.

And @Gerg noted the family resemblance:

add-access-rule kinda sounds like a synonym of add-network-policy

Using --allow-from-* reads more imperatively but diverges from the source → destination mental model that CF networking users already know. Keeping --source-app / --source-space / --source-org makes the two commands feel like siblings, which is the right outcome given the consistency feedback received during RFC review.

@rkoster

rkoster commented Jun 17, 2026

Copy link
Copy Markdown
Author

Issue: add-route-policy and remove-route-policy do not have matching flags.
There is no matching --source-app for the remove policy.

Fixed in 0ab3e02. The source flags (--source-app, --source-space, --source-org, --source-any, --source) are now extracted into a shared RoutePolicySourceFlags struct embedded in both commands. remove-route-policy now accepts exactly the same source flags as add-route-policy, so the example from the issue now works:

cf remove-route-policy apps.identity --source-app frontend --hostname backend

@rkoster

rkoster commented Jun 17, 2026

Copy link
Copy Markdown
Author

Done in fba97d8 — here's what was added:

ccversion.MinVersionRoutePolicies — placeholder constant "3.999.0" in api/cloudcontroller/ccversion/minimum_version.go. The comment in-file calls out that it must be replaced with the real CAPI version once the team confirms it.

Failing testapi/cloudcontroller/ccversion/minimum_version_test.go contains TestMinVersionRoutePoliciesIsNotPlaceholder which fails as long as the constant holds "3.999.0", so the TODO won't be silently forgotten.

Version checks added:

  • add-route-policy, remove-route-policy, route-policies — unconditional MinimumCCAPIVersionCheck at top of Execute()
  • create-shared-domain, create-private-domain — conditional check (fires only when --enforce-route-policies is passed), using "--enforce-route-policies" as the feature name in the error message

Let me know what version number to drop in once CAPI has it.

@rkoster rkoster force-pushed the enhance-add-access-rule-ux branch 2 times, most recently from 2a42b90 to 4b6445b Compare June 17, 2026 11:27
rkoster added 10 commits June 17, 2026 13:33
This commit improves the user experience for the add-access-rule command
by replacing the positional GUID-based SELECTOR argument with intuitive
flags that accept human-readable names and support cross-space/org resolution.

Changes:

**Command Interface:**
- Remove positional SELECTOR argument (breaking change, acceptable for unreleased feature)
- Add new flags: --source-app, --source-space, --source-org, --source-any, --selector
- Support hierarchical name resolution:
  - --source-app APP_NAME (looks in current space)
  - --source-app APP_NAME --source-space SPACE (cross-space in current org)
  - --source-app APP_NAME --source-space SPACE --source-org ORG (cross-org)
  - --source-space SPACE (space-level rule)
  - --source-org ORG (org-level rule)
  - --source-any (allow any authenticated app)
  - --selector SELECTOR (raw GUID-based selector for advanced users)
- Validate exactly one primary source is specified
- Display verbose output showing resolved selector for transparency

**Terminology Update:**
- Rename all "target" terminology to "source" throughout codebase
- Access rules specify the source (who can access), not the target
- Update AccessRuleWithRoute.TargetName → SourceName
- Update resolveAccessRuleTarget() → resolveAccessRuleSource()
- Update access-rules list command table header: "target" → "source"

**Error Handling:**
- Provide helpful error messages when app not found in current space
- Suggest using --source-space and --source-org flags for cross-space/org access
- Follow CF CLI patterns from add-network-policy command

**Testing:**
- Add 17 comprehensive test cases for add-access-rule command
- Update 19 actor tests to use new SourceName field
- All tests passing (36/36)

**Domain Integration:**
- Add enforce_access_rules support to create-shared-domain and create-private-domain
- Add --enforce-access-rules and --access-rules-scope flags
- Update domain resource with new fields

Examples:

  # Simple case - app in current space
  cf add-access-rule allow-frontend apps.identity --source-app frontend-app --hostname backend

  # Cross-space access
  cf add-access-rule allow-other apps.identity --source-app api-client --source-space other-space --hostname backend

  # Cross-org access
  cf add-access-rule allow-prod apps.identity --source-app client --source-space prod-space --source-org prod-org --hostname api

  # Space-level rule
  cf add-access-rule allow-monitoring apps.identity --source-space monitoring --hostname api

  # Org-level rule
  cf add-access-rule allow-platform apps.identity --source-org platform --hostname shared-api

  # Any authenticated app
  cf add-access-rule allow-all apps.identity --source-any --hostname public-api

Related to: cloudfoundry/community#1438
Per RFC commits 882b69a and 11752f2, access rules no longer have
user-provided names. They are identified by their selector only,
with labels/annotations used for metadata instead.

Changes:
- Removed RULE_NAME argument from add-access-rule command
- Removed Name field from AccessRule API resource
- Updated access-rules list to show 4 columns (route, selector, scope, source)
  - SourceName now represents resolved app/space/org name from selector
- Updated remove-access-rule to use --selector flag instead of rule name
- Renamed DeleteAccessRule() to DeleteAccessRuleBySelector()
- Updated all tests to remove Name field references

All tests passing.
…lumns

Changed table format from:
  route                    selector  scope  source
  backend.apps.identity    ...       app    frontend-app

To:
  host     domain          path     selector  scope  source
  backend  apps.identity            ...       app    frontend-app
  api      apps.identity   /metrics ...       space  monitoring

This provides better clarity by separating the route components into
individual columns, making it easier to scan and filter visually.
…urce

Complete terminology shift for identity-aware routing RFC implementation:

**Access Rules → Route Policies**
- API: /v3/access_rules → /v3/route_policies
- CLI commands:
  - cf access-rules → cf route-policies
  - cf add-access-rule → cf add-route-policy
  - cf remove-access-rule → cf remove-route-policy
- Domain flags: --enforce-access-rules → --enforce-route-policies
- Domain fields: enforce_access_rules → enforce_route_policies,
  access_rules_scope → route_policies_scope

**Selector → Source**
- API field: "selector" → "source"
- CLI flag: --selector → --source
- Query params: selectors → sources, selector_resource_guids → source_guids
- Table column headers: "selector/source" → "source/name"
- Internal types: AccessRule → RoutePolicy, AccessRuleWithRoute → RoutePolicyWithRoute
- Error types: AccessRuleNotFoundError → RoutePolicyNotFoundError

**Rationale (per RFC)**
- "Route policies" aligns with existing CF "network policies" terminology
- "Source" matches C2C network policy convention (source → destination)
- Improves clarity: policies define allowed sources that can reach routes
- Better mental model for users familiar with CF networking concepts

This is a breaking change but acceptable since RFC is pre-GA with only
POC/lab implementations. Clean terminology is preferred over backward
compatibility at this stage.

Co-authored-by: RFC Community <cloudfoundry/community#1438>
Aligns-with: cloudfoundry/community@be8d74c1
Extract source resolution flags (--source-app, --source-space, --source-org,
--source-any, --source) into a shared RoutePolicySourceFlags struct embedded
in both add-route-policy and remove-route-policy commands.

Previously remove-route-policy only accepted --source with a raw GUID-format
value (cf:app:<guid>, etc.), while add-route-policy supported name-based
resolution. The two commands now have matching flag sets.
Guard add-route-policy, remove-route-policy, and route-policies with an
unconditional MinimumCCAPIVersionCheck against MinVersionRoutePolicies.
Guard create-shared-domain and create-private-domain conditionally when
--enforce-route-policies is passed.

MinVersionRoutePolicies is currently a placeholder (3.999.0); a failing
test in ccversion/minimum_version_test.go keeps the TODO visible until the
real CAPI version is confirmed and the constant is updated.
Show a single 'route policies' column when the CAPI version supports it.
The column is blank for plain domains, 'enforced' when enforcement is on
with no scope, and 'enforced (org/space/any)' when a scope is set.

The column is gated on MinVersionRoutePolicies so it silently disappears
on older CAPI targets — no hard error, cf domains still works everywhere.
…sing

When a user specifies --source-org with --source-app but omits --source-space,
validateSourceFlags() previously passed (treating --source-app as the sole
primary flag), and resolveSource() silently ignored --source-org, resolving
the app in the currently targeted space.

Add a pre-check in validateSourceFlags() that returns RequiredFlagsError
(--source-org and --source-space must be used together) whenever --source-org
is combined with --source-app but --source-space is absent.
@rkoster rkoster force-pushed the enhance-add-access-rule-ux branch from 4b6445b to cc26579 Compare June 17, 2026 11:34
rkoster added 5 commits June 17, 2026 14:27
…st.go

Refactor CreatePrivateDomain describe block to use JustBeforeEach pattern
and add Context block for enforceAccessRules=true with non-empty scope,
mirroring the existing coverage in CreateSharedDomain.
…cyArgs

Three identical one-field structs replaced with a single shared type.
Description aligned with the existing convention in arguments.go.
…ivate-domain new flags

- route_policy_source_flags_test.go: covers all validateSourceFlags branches
  (no flags, single flags, qualifier combinations, RequiredFlagsError,
  ArgumentCombinationError) and all resolveSource paths (raw --source,
  --source-any, --source-app with/without cross-space/org, --source-space,
  --source-org, error propagation from each actor call)
- create_private_domain_command_test.go: adds coverage for --scope without
  --enforce-route-policies, invalid --scope values, API version check failure,
  --enforce-route-policies success (identity-aware TIP), and --scope forwarding
…red-domain test

Mirrors the coverage added to create_private_domain_command_test.go:
- --scope without --enforce-route-policies returns an error
- invalid --scope value returns an error
- --enforce-route-policies with old API version returns MinimumCFAPIVersionNotMetError
- --enforce-route-policies success: identity-aware TIP, enforce=true passed to actor
- --enforce-route-policies + --scope: scope forwarded to actor
- default path now explicitly asserts enforce=false, scope empty
…viour

Introduce EnforceRoutePoliciesBehavior and ItEnforcesRoutePolicies in
enforce_route_policies_shared_test.go. Both create-shared-domain and
create-private-domain tests now delegate the duplicate When blocks to the
shared helper, parameterised only by TIPAdjective and the actor-specific
arg-extraction closures. ccversion and translatableerror imports removed from
both individual test files.
rkoster added 2 commits June 17, 2026 16:16
Bulk find-and-replace tooling from the rebrand commit introduced
spaces→tabs indentation fixes and Invocations() mutex-lock removals
in files completely unrelated to the route-policies feature. Restore
all of them to origin/main to keep the feature diff focused.
// version that introduces /v3/route_policies and the enforce_route_policies /
// route_policies_scope domain fields. Replace "3.999.0" with the real version
// once known. The test in minimum_version_test.go will keep failing until then.
MinVersionRoutePolicies = "3.999.0"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version should be changed once: cloudfoundry/cloud_controller_ng#4910 has been merged and released.

@rkoster rkoster marked this pull request as ready for review June 17, 2026 14:46
@rkoster

rkoster commented Jun 17, 2026

Copy link
Copy Markdown
Author

This PR is ready for review. The specs are intentionally failing until the correct CAPI version has been filled in. But let's try and get this PR in a reviewed state, so the changes in this PR can get released closely after the CAPI changes get published.

@rkoster rkoster requested review from jcvrabo and prkalle June 17, 2026 14:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants