Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ setup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist
echo "Creating Kind cluster '$(KIND_CLUSTER)'..."; \
$(KIND) create cluster --name $(KIND_CLUSTER) ;; \
esac
$(KIND) export kubeconfig --name $(KIND_CLUSTER)

.PHONY: test-e2e
test-e2e: setup-test-e2e manifests generate fmt vet ## Run the e2e tests. Use RUN_ONLY=<context> to focus specs.
Expand Down
33 changes: 15 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,32 +206,29 @@ Redis ACL rules are configured through `spec.aclRules`. The operator always owns
# Controller configuration
All of these settings are optional, and have safe defaults. If you don't need to change any of these settings, you can skip this section.

Only 1 controller resource is allowed, if there are more than 1, the operator will log an error and ignore all but the first one it finds.


## Cross-namespace existing secrets (optional)
By default, the `existingSecret` value is resolved in the same namespace as the `PostgresAccess` resource.

To allow cross-namespace references, create exactly one `Controller` resource as found in `config/samples/access_v1_controller.yaml` in the same namespace as the operator.
To allow cross-namespace references, create `ConfigMap/access-operator-settings` as shown in `config/samples/access_v1_controller.yaml` in the same namespace as the operator.
Excluded usernames are skipped during normal reconciliation and orphan cleanup, so the operator will not create, update, or delete those roles.

## Ignoring users (optional)
By default, no users are ignored **except** for Postgres users that can't login (rolcanlogin == false) or are superusers (rolsuper == true).
To exclude certain usernames from being managed by the operator, you can specify them in the `Controller` resource as well.
To exclude certain usernames from being managed by the operator, you can specify them in the operator settings ConfigMap as well.
This is useful for excluding default users like `postgres` or `admin` that are created by the service itself for example.

Add the service's key (postgres, rabbitmq, redis) within the settings as shown here in the CR to exclude the `postgres`, `admin`, and `default` users:
Add the service's key (postgres, rabbitmq, redis) within the embedded config document to exclude the `postgres`, `admin`, and `default` users:
```yaml
spec:
settings:
existingSecretNamespace: false
postgres:
excludedUsers:
- postgres
rabbitmq:
excludedUsers:
- admin
redis:
excludedUsers:
- default
existingSecretNamespace: false
postgres:
excludedUsers:
- postgres
rabbitmq:
excludedUsers:
- admin
redis:
excludedUsers:
- default
```

You can also control stale-user cleanup per backend from the same singleton `Controller` resource. The safe default is `Restrict`, which retains users that are no longer referenced by any managed access resource. For PostgreSQL, the controller-scoped policy uses `Cascade`, `Restrict`, `Orphan`, or `Retain`. `Retain` disables stale-user cleanup during steady-state reconciliation but still allows the specific `PostgresAccess` being deleted to finalize its own role. Redis and RabbitMQ use `Delete` or `Restrict`.
110 changes: 7 additions & 103 deletions api/v1/controller_deepcopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,74 +16,11 @@ limitations under the License.

package v1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)

// DeepCopyInto copies all properties of this object into another object of the same type.
func (in *Controller) DeepCopyInto(out *Controller) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}

// DeepCopy returns a deep copy of this object.
func (in *Controller) DeepCopy() *Controller {
if in == nil {
return nil
}
out := new(Controller)
in.DeepCopyInto(out)
return out
}

// DeepCopyObject returns a generically typed copy of this object.
func (in *Controller) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

// DeepCopyInto copies all properties of this object into another object of the same type.
func (in *ControllerList) DeepCopyInto(out *ControllerList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Controller, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}

// DeepCopy returns a deep copy of this object.
func (in *ControllerList) DeepCopy() *ControllerList {
if in == nil {
return nil
}
out := new(ControllerList)
in.DeepCopyInto(out)
return out
}

// DeepCopyObject returns a generically typed copy of this object.
func (in *ControllerList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

// DeepCopyInto copies all properties of this object into another object of the same type.
func (in *ControllerSettings) DeepCopyInto(out *ControllerSettings) {
*out = *in
in.PostgresSettings.DeepCopyInto(&out.PostgresSettings)
in.RabbitMQSettings.DeepCopyInto(&out.RabbitMQSettings)
in.RedisSettings.DeepCopyInto(&out.RedisSettings)
}

// DeepCopy returns a deep copy of this object.
Expand All @@ -96,12 +33,6 @@ func (in *ControllerSettings) DeepCopy() *ControllerSettings {
return out
}

// DeepCopyInto copies all properties of this object into another object of the same type.
func (in *ControllerSpec) DeepCopyInto(out *ControllerSpec) {
*out = *in
in.Settings.DeepCopyInto(&out.Settings)
}

// DeepCopyInto copies all properties of this object into another object of the same type.
func (in *PostgresControllerSettings) DeepCopyInto(out *PostgresControllerSettings) {
*out = *in
Expand All @@ -110,6 +41,11 @@ func (in *PostgresControllerSettings) DeepCopyInto(out *PostgresControllerSettin
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.StaleUserDeletionPolicy != nil {
in, out := &in.StaleUserDeletionPolicy, &out.StaleUserDeletionPolicy
*out = new(PostgresCleanupPolicy)
**out = **in
}
}

// DeepCopy returns a deep copy of this object.
Expand All @@ -121,35 +57,3 @@ func (in *PostgresControllerSettings) DeepCopy() *PostgresControllerSettings {
in.DeepCopyInto(out)
return out
}

// DeepCopy returns a deep copy of this object.
func (in *ControllerSpec) DeepCopy() *ControllerSpec {
if in == nil {
return nil
}
out := new(ControllerSpec)
in.DeepCopyInto(out)
return out
}

// DeepCopyInto copies all properties of this object into another object of the same type.
func (in *ControllerStatus) DeepCopyInto(out *ControllerStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]metav1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}

// DeepCopy returns a deep copy of this object.
func (in *ControllerStatus) DeepCopy() *ControllerStatus {
if in == nil {
return nil
}
out := new(ControllerStatus)
in.DeepCopyInto(out)
return out
}
87 changes: 32 additions & 55 deletions api/v1/controller_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,18 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

// +kubebuilder:object:generate=true
package v1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// StaleUserDeletionPolicy defines how the controller handles managed users
// that are no longer referenced by any managed access resource.
// +kubebuilder:validation:Enum=Delete;Restrict
type StaleUserDeletionPolicy string

const (
// StaleUserDeletionPolicyDelete removes unreferenced managed users.
StaleUserDeletionPolicyDelete StaleUserDeletionPolicy = "Delete"
// StaleUserDeletionPolicyRestrict retains unreferenced managed users.
StaleUserDeletionPolicyRestrict StaleUserDeletionPolicy = "Restrict"
)

// StaleVhostDeletionPolicy defines how the controller handles RabbitMQ vhosts
Expand Down Expand Up @@ -53,6 +60,13 @@ type RabbitMQControllerSettings struct {
// +optional
// +kubebuilder:default="Retain"
StaleVhostDeletionPolicy *StaleVhostDeletionPolicy `json:"staleVhostDeletionPolicy,omitempty"`

// staleUserDeletionPolicy controls whether the controller deletes RabbitMQ
// users that are no longer referenced by any managed RabbitMQAccess.
// Restrict retains stale users instead of deleting them.
// +optional
// +kubebuilder:default="Restrict"
StaleUserDeletionPolicy *StaleUserDeletionPolicy `json:"staleUserDeletionPolicy,omitempty"`
}

type PostgresControllerSettings struct {
Expand All @@ -62,6 +76,14 @@ type PostgresControllerSettings struct {
// +listType=set
// +optional
ExcludedUsers []string `json:"excludedUsers,omitempty"`

// staleUserDeletionPolicy controls whether the controller deletes PostgreSQL
// roles that are no longer referenced by any managed PostgresAccess.
// Restrict retains stale roles, while Retain only permits deletion during
// finalization of the specific PostgresAccess being removed.
// +optional
// +kubebuilder:default="Restrict"
StaleUserDeletionPolicy *PostgresCleanupPolicy `json:"staleUserDeletionPolicy,omitempty"`
}

type RedisControllerSettings struct {
Expand All @@ -71,6 +93,13 @@ type RedisControllerSettings struct {
// +listType=set
// +optional
ExcludedUsers []string `json:"excludedUsers,omitempty"`

// staleUserDeletionPolicy controls whether the controller deletes Redis ACL
// users that are no longer referenced by any managed RedisAccess.
// Restrict retains stale users instead of deleting them.
// +optional
// +kubebuilder:default="Restrict"
StaleUserDeletionPolicy *StaleUserDeletionPolicy `json:"staleUserDeletionPolicy,omitempty"`
}

// ControllerSettings defines operator-wide behavior toggles.
Expand All @@ -93,55 +122,3 @@ type ControllerSettings struct {
// +optional
RedisSettings RedisControllerSettings `json:"redis,omitempty"`
}

// ControllerSpec defines the desired state of Controller.
type ControllerSpec struct {
// settings contains operator-wide settings.
// +optional
Settings ControllerSettings `json:"settings,omitempty"`
}

// ControllerStatus defines the observed state of Controller.
type ControllerStatus struct {
// conditions represent the current state of this Controller resource.
// +listType=map
// +listMapKey=type
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
}

// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:path=controllers,scope=Namespaced,singular=controller,shortName=actrl

// Controller is the Schema for the controllers API.
type Controller struct {
metav1.TypeMeta `json:",inline"`

// metadata is a standard object metadata.
// +optional
metav1.ObjectMeta `json:"metadata,omitzero"`

// spec defines the desired state of Controller.
// +optional
Spec ControllerSpec `json:"spec,omitzero"`

// status defines the observed state of Controller.
// +optional
Status ControllerStatus `json:"status,omitzero"`
}

// +kubebuilder:object:root=true
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// ControllerList contains a list of Controller.
type ControllerList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitzero"`
Items []Controller `json:"items"`
}

func init() {
SchemeBuilder.Register(&Controller{}, &ControllerList{})
}
33 changes: 14 additions & 19 deletions api/v1/postgresaccess_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ type ConnectionSpec struct {

// existingSecretNamespace is the namespace where existingSecret is stored.
// If omitted, defaults to the PostgresAccess namespace.
// Cross-namespace secret references are rejected unless a single Controller
// resource exists with spec.settings.existingSecretNamespace=true.
// Cross-namespace secret references are rejected unless the operator settings
// ConfigMap enables existingSecretNamespace=true.
// +optional
// +kubebuilder:validation:MinLength=1
ExistingSecretNamespace *string `json:"existingSecretNamespace,omitempty"`
Expand Down Expand Up @@ -100,17 +100,20 @@ type ConnectionSpec struct {
SSLMode *string `json:"sslMode,omitempty"`
}

// CleanupPolicy specifies how to handle owned objects when dropping a user
// +kubebuilder:validation:Enum=Cascade;Restrict;Orphan
type CleanupPolicy string
// PostgresCleanupPolicy specifies how the controller handles PostgreSQL role deletion.
// +kubebuilder:validation:Enum=Cascade;Restrict;Orphan;Retain
type PostgresCleanupPolicy string

const (
// CleanupPolicyCascade drops all objects owned by the user (destructive)
CleanupPolicyCascade CleanupPolicy = "Cascade"
// CleanupPolicyRestrict prevents dropping the user if they own any objects
CleanupPolicyRestrict CleanupPolicy = "Restrict"
// CleanupPolicyOrphan reassigns owned objects to the current database owner before dropping
CleanupPolicyOrphan CleanupPolicy = "Orphan"
// CleanupPolicyCascade drops all objects owned by the user (destructive).
CleanupPolicyCascade PostgresCleanupPolicy = "Cascade"
// CleanupPolicyRestrict prevents dropping the user if they own any objects.
CleanupPolicyRestrict PostgresCleanupPolicy = "Restrict"
// CleanupPolicyOrphan reassigns owned objects to the current database owner before dropping.
CleanupPolicyOrphan PostgresCleanupPolicy = "Orphan"
// CleanupPolicyRetain retains stale roles during steady-state reconciliation and
// only allows deletion during finalization of the specific PostgresAccess.
CleanupPolicyRetain PostgresCleanupPolicy = "Retain"
)

// GrantSpec defines database grants to be applied
Expand Down Expand Up @@ -154,14 +157,6 @@ type PostgresAccessSpec struct {
// +kubebuilder:validation:MinItems=1
// +kubebuilder:validation:MaxItems=1
Grants []GrantSpec `json:"grants"`

// cleanupPolicy specifies how to handle owned objects when dropping a user
// Cascade: drops all objects owned by the user (destructive)
// Restrict: prevents dropping the user if they own any objects (safe default)
// Orphan: reassigns owned objects to the current database owner before dropping
// +optional
// +kubebuilder:default="Restrict"
CleanupPolicy *CleanupPolicy `json:"cleanupPolicy,omitempty"`
}

// ReconcileState defines the most recent reconcile outcome.
Expand Down
Loading
Loading