Skip to content

feat: add OpenZiti service creation to registration and update identity cleanup #8

@rowan-stein

Description

@rowan-stein

User Request

Per the runner/app re-enrollment change doc, the Apps service needs to:

  • Create the per-app OpenZiti service during app creation (registration), not enrollment
  • Update enrollment to use the simplified Ziti Management response (no `ziti_service_id`)
  • Update deletion to use the new `DeleteAppIdentity` request format (`identity_id` instead of `ziti_identity_id`)

This is Phase 4 of the 4-phase implementation plan. Depends on:

  • ✅ Phase 1: Proto changes (agynio/api PR #92 — merged)
  • ✅ Phase 2: Ziti Management changes (agynio/ziti-management PR #32 — approved, pending merge)

Specification

1. Update Proto Stubs

Regenerate proto stubs from BSR to pick up changes from agynio/api PR #92:

  • `CreateAppIdentityResponse`: `ziti_service_id` field is now reserved (removed)
  • `DeleteAppIdentityRequest`: `ziti_identity_id` → `identity_id`; `ziti_service_id` remains
  • New RPC: `CreateService` / `CreateServiceRequest` / `CreateServiceResponse`

Run `buf generate` to regenerate.

2. CreateApp Changes

After registering the identity and before inserting the app record, create the per-app OpenZiti service:

svcResp, err := s.zitiManagementClient.CreateService(ctx, &zitimanagementv1.CreateServiceRequest{
    Name:           fmt.Sprintf("app-%s", slug),
    RoleAttributes: []string{"app-services"},
})
if err != nil {
    s.cleanupAuthorization(ctx, identityID)
    return nil, status.Errorf(codes.Internal, "create ziti service: %v", err)
}

Store the returned `ZitiServiceId` in the app record:

app, err := s.store.CreateApp(ctx, store.CreateAppInput{
    // ... existing fields ...
    ZitiServiceID: svcResp.GetZitiServiceId(),
})

The `ziti_service_id` column already exists in the DB and store — it just needs to be populated at creation time.

3. EnrollApp Changes

The `CreateAppIdentityResponse` no longer includes `ziti_service_id`. Update the enrollment flow:

Before (current code):

s.store.UpdateAppZitiIdentity(ctx, app.Meta.ID, zitiResp.GetZitiIdentityId(), zitiResp.GetZitiServiceId())

After:

s.store.UpdateAppZitiIdentity(ctx, app.Meta.ID, zitiResp.GetZitiIdentityId(), app.ZitiServiceID)

The service ID is now read from the existing app record (set during CreateApp), not from the enrollment response.

Also update the cleanup-on-failure path to use the correct service ID:

if err := s.store.UpdateAppZitiIdentity(...); err != nil {
    s.cleanupZitiIdentity(ctx, app.IdentityID, zitiResp.GetZitiIdentityId(), app.ZitiServiceID)
    // ...
}

4. DeleteApp Changes — cleanupZitiIdentity

The `DeleteAppIdentityRequest` now uses `identity_id` (platform app identity UUID) instead of `ziti_identity_id`. Update `cleanupZitiIdentity`:

Before:

func (s *Server) cleanupZitiIdentity(ctx context.Context, zitiIdentityID string, zitiServiceID string) {
    s.zitiManagementClient.DeleteAppIdentity(ctx, &zitimanagementv1.DeleteAppIdentityRequest{
        ZitiIdentityId: zitiIdentityID,
        ZitiServiceId:  zitiServiceID,
    })
}

After:

func (s *Server) cleanupZitiIdentity(ctx context.Context, identityID uuid.UUID, zitiServiceID string) {
    s.zitiManagementClient.DeleteAppIdentity(ctx, &zitimanagementv1.DeleteAppIdentityRequest{
        IdentityId:    identityID.String(),
        ZitiServiceId: zitiServiceID,
    })
}

Note the signature change: takes platform `identityID` (UUID) instead of `zitiIdentityID` (string).

5. Update All Callers of cleanupZitiIdentity

After changing the signature, update all call sites:

DeleteApp:

// Before:
if app.ZitiIdentityID != "" {
    s.cleanupZitiIdentity(ctx, app.ZitiIdentityID, app.ZitiServiceID)
}
// After:
if app.ZitiServiceID != "" {
    s.cleanupZitiIdentity(ctx, app.IdentityID, app.ZitiServiceID)
}

The guard condition changes from checking `ZitiIdentityID` to checking `ZitiServiceID`, because:

  • The service is always created at registration time (always present if app was properly created)
  • The identity may not exist (never enrolled) — but DeleteAppIdentity handles that gracefully (logs + continues to delete service)

EnrollApp cleanup-on-failure:

// Before:
s.cleanupZitiIdentity(ctx, zitiResp.GetZitiIdentityId(), zitiResp.GetZitiServiceId())
// After:
s.cleanupZitiIdentity(ctx, app.IdentityID, app.ZitiServiceID)

EnrollApp pre-enrollment cleanup (existing defense-in-depth):

// Before:
if app.ZitiIdentityID != "" {
    s.cleanupZitiIdentity(ctx, app.ZitiIdentityID, app.ZitiServiceID)
}
// After — REMOVE this block entirely

The pre-enrollment cleanup in EnrollApp can be removed because `CreateAppIdentity` in Ziti Management now handles idempotent cleanup internally. Keeping it would be redundant and could cause issues (trying to delete an identity that Ziti Management will also try to delete).

6. Update Tests

Update tests in `internal/server/server_test.go` to account for:

  • `CreateService` call in `CreateApp`
  • Changed `EnrollApp` response (no `ziti_service_id` from enrollment response)
  • Updated `DeleteAppIdentity` request format (`identity_id`)
  • Updated `cleanupZitiIdentity` signature

Notes

  • No DB migration needed — `ziti_service_id` column already exists, just needs to be populated during `CreateApp`
  • `EnrollAppResponse` proto already has the correct shape (`identity_json` + `identity_id`)
  • Deployment order: Ziti Management must be deployed first

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions