|
| 1 | +# OpenStackClusterIdentity for Centralized Credential Management |
| 2 | + |
| 3 | +## Metadata |
| 4 | + |
| 5 | +- **Authors**: @bnallapeta |
| 6 | +- **Reviewers**: CAPO maintainers |
| 7 | +- **Status**: Proposed |
| 8 | +- **Creation Date**: 2025-07-22 |
| 9 | +- **Last Updated**: 2025-07-22 |
| 10 | + |
| 11 | +## Summary |
| 12 | + |
| 13 | +This proposal introduces `OpenStackClusterIdentity`, a cluster-scoped resource for centralized OpenStack credential management in CAPO. This enables multi-tenant environments to share credentials across namespaces while maintaining proper access controls, following patterns from AWS and Azure Cluster API providers. |
| 14 | + |
| 15 | +## Motivation |
| 16 | + |
| 17 | +### Goals |
| 18 | +- Enable centralized storage of OpenStack credentials in cluster-scoped resources |
| 19 | +- Provide fine-grained namespace access controls for credential usage |
| 20 | +- Maintain 100% backward compatibility with existing OpenStackCluster resources |
| 21 | +- Support manual, gradual migration without breaking existing deployments |
| 22 | +- Follow established patterns from other Cluster API providers (AWS, Azure) |
| 23 | + |
| 24 | +### Non-Goals |
| 25 | +- Automatic migration of existing deployments |
| 26 | +- Integration with external secret management systems |
| 27 | +- Breaking changes to existing API or functionality |
| 28 | + |
| 29 | +### User Stories |
| 30 | + |
| 31 | +#### Story 1: Platform Administrator |
| 32 | +As a platform administrator managing multiple tenant namespaces, I want to store OpenStack credentials centrally in a secure namespace (e.g., `capo-system`), control which tenant namespaces can use specific credentials, and rotate credentials in one place without updating every namespace. |
| 33 | + |
| 34 | +#### Story 2: Tenant User |
| 35 | +As a tenant user in namespace `team-a`, I want to create OpenStack clusters using centrally managed credentials without managing OpenStack secrets in my namespace, with clear error messages if I don't have permission to use specific credentials. |
| 36 | + |
| 37 | +#### Story 3: Multi-Region Setup |
| 38 | +As an administrator managing clusters across multiple OpenStack regions, I want to create region-specific cluster identities with appropriate credentials and allow tenants to use different regional credentials based on their needs. |
| 39 | + |
| 40 | +### API Design |
| 41 | + |
| 42 | +#### New OpenStackClusterIdentity Resource (Cluster-scoped) |
| 43 | + |
| 44 | +```go |
| 45 | +type OpenStackClusterIdentity struct { |
| 46 | + metav1.TypeMeta `json:",inline"` |
| 47 | + metav1.ObjectMeta `json:"metadata,omitempty"` |
| 48 | + Spec OpenStackClusterIdentitySpec `json:"spec,omitempty"` |
| 49 | + Status OpenStackClusterIdentityStatus `json:"status,omitempty"` |
| 50 | +} |
| 51 | + |
| 52 | +type OpenStackClusterIdentitySpec struct { |
| 53 | + // SecretRef references the secret containing OpenStack credentials |
| 54 | + SecretRef OpenStackCredentialSecretReference `json:"secretRef"` |
| 55 | + |
| 56 | + // AllowedNamespaces defines which namespaces can use this identity |
| 57 | + // +optional |
| 58 | + AllowedNamespaces []string `json:"allowedNamespaces,omitempty"` |
| 59 | + |
| 60 | + // NamespaceSelector selects allowed namespaces via labels |
| 61 | + // +optional |
| 62 | + NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty"` |
| 63 | +} |
| 64 | + |
| 65 | +type OpenStackCredentialSecretReference struct { |
| 66 | + Name string `json:"name"` |
| 67 | + Namespace string `json:"namespace"` |
| 68 | +} |
| 69 | + |
| 70 | +type OpenStackClusterIdentityStatus struct { |
| 71 | + Ready bool `json:"ready"` |
| 72 | + Conditions clusterv1.Conditions `json:"conditions,omitempty"` |
| 73 | +} |
| 74 | + |
| 75 | +type OpenStackClusterIdentityReference struct { |
| 76 | + Name string `json:"name"` |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +**Enhanced OpenStackCluster:** |
| 81 | +```go |
| 82 | +type OpenStackClusterSpec struct { |
| 83 | + // ... existing fields unchanged ... |
| 84 | + |
| 85 | + // ClusterIdentityRef references a cluster-scoped identity (NEW) |
| 86 | + // Takes precedence over IdentityRef if specified |
| 87 | + // +optional |
| 88 | + ClusterIdentityRef *OpenStackClusterIdentityReference `json:"clusterIdentityRef,omitempty"` |
| 89 | + |
| 90 | + // IdentityRef references namespace-local secret (EXISTING - unchanged) |
| 91 | + // +kubebuilder:validation:Required |
| 92 | + IdentityRef OpenStackIdentityReference `json:"identityRef"` |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +### Implementation Details |
| 97 | + |
| 98 | +#### Credential Resolution Logic |
| 99 | +The scope factory will implement dual-path credential resolution: |
| 100 | + |
| 101 | +```go |
| 102 | +func (f *providerScopeFactory) resolveCredentials(obj infrav1.IdentityRefProvider) { |
| 103 | + // Priority 1: Check for cluster identity reference |
| 104 | + if clusterRef := obj.GetClusterIdentityRef(); clusterRef != nil { |
| 105 | + return f.newScopeFromClusterIdentity(clusterRef) |
| 106 | + } |
| 107 | + |
| 108 | + // Priority 2: Fall back to existing namespace identity behavior |
| 109 | + return f.newScopeFromNamespaceIdentity(obj.GetIdentityRef()) |
| 110 | +} |
| 111 | +``` |
| 112 | + |
| 113 | +**Key Edge Cases:** |
| 114 | +- Both references specified: Use `clusterIdentityRef`, log warning |
| 115 | +- Identity deletion: Clusters show degraded status, don't fail |
| 116 | +- Permission changes: Dynamic re-validation during reconciliation |
| 117 | +- Invalid access: Clear error messages with namespace authorization checks |
| 118 | + |
| 119 | +**RBAC Requirements:** |
| 120 | +```yaml |
| 121 | +rules: |
| 122 | +- apiGroups: [""] |
| 123 | + resources: ["secrets", "namespaces"] |
| 124 | + verbs: ["get"] |
| 125 | +- apiGroups: ["infrastructure.cluster.x-k8s.io"] |
| 126 | + resources: ["openstackclusteridentities"] |
| 127 | + verbs: ["get", "list", "watch"] |
| 128 | +``` |
| 129 | +
|
| 130 | +### Backward Compatibility |
| 131 | +
|
| 132 | +- Existing `identityRef` field remains required and fully functional |
| 133 | +- Clusters without `clusterIdentityRef` work exactly as before |
| 134 | +- `clusterIdentityRef` takes precedence when both are specified |
| 135 | + |
| 136 | +#### Migration Strategy |
| 137 | +- **No forced migration**: Existing deployments continue working indefinitely |
| 138 | +- **Manual opt-in**: Users choose when to adopt cluster identities |
| 139 | +- **Gradual adoption**: Mix of old and new approaches supported |
| 140 | +- **Clear documentation**: Step-by-step migration guides and examples |
| 141 | + |
| 142 | +### Testing Strategy |
| 143 | + |
| 144 | +**Unit Tests**: API validation, dual-path credential resolution, RBAC permission checking, edge cases |
| 145 | +**Integration Tests**: End-to-end credential resolution, cross-namespace access validation, permission enforcement |
| 146 | +**E2E Tests**: Full cluster lifecycle with cluster identity, mixed deployments, runtime permission changes |
| 147 | +**Security Tests**: Unauthorized access attempts, RBAC boundary enforcement, audit trail verification |
| 148 | + |
| 149 | +## Risks and Mitigations |
| 150 | + |
| 151 | +| Risk | Mitigation | |
| 152 | +|------|------------| |
| 153 | +| Cross-namespace RBAC complexity | Extensive testing, clear documentation, validation webhooks | |
| 154 | +| Security boundary violations | Strict validation, audit logging, security review | |
| 155 | +| Migration complexity | Clear documentation, examples, optional migration | |
| 156 | +| Performance impact | Caching strategy, minimal additional overhead | |
| 157 | + |
| 158 | +## Alternatives |
| 159 | + |
| 160 | +**External Secret Operator**: More complex, adds external dependency |
| 161 | +**OpenStack Application Credentials**: Not universally supported across deployments |
| 162 | +**Namespace-scoped Identity**: Doesn't solve centralized management |
| 163 | +**ConfigMap-based References**: No validation, security concerns |
| 164 | + |
| 165 | +## Example Usage |
| 166 | + |
| 167 | +```yaml |
| 168 | +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 |
| 169 | +kind: OpenStackClusterIdentity |
| 170 | +metadata: |
| 171 | + name: production-openstack |
| 172 | +spec: |
| 173 | + secretRef: |
| 174 | + name: openstack-credentials |
| 175 | + namespace: capo-system |
| 176 | + allowedNamespaces: [team-a, team-b] |
| 177 | +
|
| 178 | +--- |
| 179 | +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 |
| 180 | +kind: OpenStackClusterIdentity |
| 181 | +metadata: |
| 182 | + name: development-openstack |
| 183 | +spec: |
| 184 | + secretRef: |
| 185 | + name: dev-openstack-credentials |
| 186 | + namespace: capo-system |
| 187 | + namespaceSelector: |
| 188 | + matchLabels: |
| 189 | + environment: "development" |
| 190 | +``` |
| 191 | + |
| 192 | +### Use in OpenStackCluster |
| 193 | +```yaml |
| 194 | +apiVersion: infrastructure.cluster.x-k8s.io/v1beta1 |
| 195 | +kind: OpenStackCluster |
| 196 | +metadata: |
| 197 | + name: my-cluster |
| 198 | + namespace: team-a |
| 199 | +spec: |
| 200 | + clusterIdentityRef: |
| 201 | + name: production-openstack |
| 202 | + identityRef: # Fallback for backward compatibility |
| 203 | + name: fallback-secret |
| 204 | + cloudName: openstack |
| 205 | +``` |
| 206 | + |
| 207 | +## Implementation Notes |
| 208 | + |
| 209 | +**Controller Changes**: Modify `pkg/scope/provider.go` for dual-path resolution, update cluster controller for identity validation |
| 210 | +**API Generation**: Update CRD generation, generate deepcopy/clients/informers, update webhooks |
| 211 | +**Documentation**: API reference, migration guides, security best practices |
| 212 | + |
| 213 | +This proposal provides centralized credential management while maintaining full backward compatibility and following established Kubernetes patterns. |
0 commit comments