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
4 changes: 2 additions & 2 deletions apis/sql/postgresql/v1alpha1/extension_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ import (
"context"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
client "sigs.k8s.io/controller-runtime/pkg/client"

xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/reference"
reference "github.com/crossplane/crossplane-runtime/pkg/reference"
"github.com/pkg/errors"
)

Expand Down
236 changes: 226 additions & 10 deletions apis/sql/postgresql/v1alpha1/grant_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,19 @@ import (
"context"
Copy link
Collaborator

Choose a reason for hiding this comment

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

This file with all its contents will be injected into provider sql and as I understand more GRANTS will be issued is that right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is copy pasted direclty from the provider-sql. We import it to be able to create valid CRs for the provider.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Will you add those CRs in another PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is them being added :D


metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
client "sigs.k8s.io/controller-runtime/pkg/client"

xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
"github.com/crossplane/crossplane-runtime/pkg/reference"
reference "github.com/crossplane/crossplane-runtime/pkg/reference"
"github.com/pkg/errors"
)

const (
errNoPrivileges = "privileges not passed"
errUnknownGrant = "cannot identify grant type based on passed params"
errMemberOfWithPrivileges = "cannot set privileges in the same grant as memberOf"
)

// A GrantSpec defines the desired state of a Grant.
type GrantSpec struct {
xpv1.ResourceSpec `json:",inline"`
Expand All @@ -43,24 +49,169 @@ type GrantPrivilege string
// +kubebuilder:validation:MinItems:=1
type GrantPrivileges []GrantPrivilege

type GrantType string

// GrantType is the list of the possible grant types represented by a GrantParameters
const (
RoleMember GrantType = "ROLE_MEMBER"
RoleDatabase GrantType = "ROLE_DATABASE"
RoleSchema GrantType = "ROLE_SCHEMA"
RoleTable GrantType = "ROLE_TABLE"
RoleSequence GrantType = "ROLE_SEQUENCE"
RoleRoutine GrantType = "ROLE_ROUTE"
RoleColumn GrantType = "ROLE_COLUMN"
RoleForeignDataWrapper GrantType = "ROLE_FOREIGN_DATA_WRAPPER"
RoleForeignServer GrantType = "ROLE_FOREIGN_SERVER"
)

type marker struct{}
type stringSet struct {
elements map[string]marker
}

func newStringSet() *stringSet {
return &stringSet{
elements: make(map[string]marker),
}
}

func (s *stringSet) add(element string) {
s.elements[element] = marker{}
}

func (s *stringSet) contains(element string) bool {
_, exists := s.elements[element]
return exists
}

func (s *stringSet) containsExactly(elements ...string) bool {
if len(s.elements) != len(elements) {
return false
}
for _, elem := range elements {
if !s.contains(elem) {
return false
}
}
return true
}

func (gp *GrantParameters) filledInFields() *stringSet {
fields := map[string]bool{
"MemberOf": gp.MemberOf != nil,
"Database": gp.Database != nil,
"Schema": gp.Schema != nil,
"Tables": len(gp.Tables) > 0,
"Columns": len(gp.Columns) > 0,
"Sequences": len(gp.Sequences) > 0,
"Routines": len(gp.Routines) > 0,
"ForeignServers": len(gp.ForeignServers) > 0,
"ForeignDataWrappers": len(gp.ForeignDataWrappers) > 0,
}
set := newStringSet()

for key, hasField := range fields {
if hasField {
set.add(key)
}
}
return set
}

var grantTypeFields = map[GrantType][]string{
RoleMember: {"MemberOf"},
RoleDatabase: {"Database"},
RoleSchema: {"Database", "Schema"},
RoleTable: {"Database", "Schema", "Tables"},
RoleColumn: {"Database", "Schema", "Tables", "Columns"},
RoleSequence: {"Database", "Schema", "Sequences"},
RoleRoutine: {"Database", "Schema", "Routines"},
RoleForeignServer: {"Database", "ForeignServers"},
RoleForeignDataWrapper: {"Database", "ForeignDataWrappers"},
}

// IdentifyGrantType return the deduced GrantType from the filled in fields.
func (gp *GrantParameters) IdentifyGrantType() (GrantType, error) {
ff := gp.filledInFields()
pc := len(gp.Privileges)

var gt *GrantType

for k, v := range grantTypeFields {
if ff.containsExactly(v...) {
gt = &k
break
}
}
if gt == nil {
return "", errors.New(errUnknownGrant)
}
if *gt == RoleMember && pc > 0 {
return "", errors.New(errMemberOfWithPrivileges)
}
if *gt != RoleMember && pc < 1 {
return "", errors.New(errNoPrivileges)
}
return *gt, nil
}

// Some privileges are shorthands for multiple privileges. These translations
// happen internally inside postgresql when making grants. When we query the
// privileges back, we need to look for the expanded set.
// https://www.postgresql.org/docs/15/ddl-priv.html
var grantReplacements = map[GrantPrivilege]GrantPrivileges{
"ALL": {"CREATE", "TEMPORARY", "CONNECT"},
"ALL PRIVILEGES": {"CREATE", "TEMPORARY", "CONNECT"},
"TEMP": {"TEMPORARY"},
var grantReplacements = map[GrantType]map[GrantPrivilege]GrantPrivileges{
RoleDatabase: {
"ALL": {"CREATE", "TEMPORARY", "CONNECT"},
"ALL PRIVILEGES": {"CREATE", "TEMPORARY", "CONNECT"},
"TEMP": {"TEMPORARY"},
},
RoleSchema: {
"ALL": {"CREATE", "USAGE"},
"ALL PRIVILEGES": {"CREATE", "USAGE"},
},
RoleTable: {
"ALL": {"SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER", "MAINTAIN"},
"ALL PRIVILEGES": {"SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER", "MAINTAIN"},
},
RoleColumn: {
"ALL": {"SELECT", "INSERT", "UPDATE", "REFERENCES"},
"ALL PRIVILEGES": {"SELECT", "INSERT", "UPDATE", "REFERENCES"},
},
RoleSequence: {
"ALL": {"USAGE", "SELECT", "UPDATE"},
"ALL PRIVILEGES": {"USAGE", "SELECT", "UPDATE"},
},
RoleRoutine: {
"ALL": {"EXECUTE"},
"ALL PRIVILEGES": {"EXECUTE"},
},
RoleForeignDataWrapper: {
"ALL": {"USAGE"},
"ALL PRIVILEGES": {"USAGE"},
},
RoleForeignServer: {
"ALL": {"USAGE"},
"ALL PRIVILEGES": {"USAGE"},
},
}

// ExpandPrivileges expands any shorthand privileges to their full equivalents.
func (gp *GrantPrivileges) ExpandPrivileges() GrantPrivileges {
func (gp *GrantParameters) ExpandPrivileges() GrantPrivileges {
gt, err := gp.IdentifyGrantType()
if err != nil {
return gp.Privileges
}
gr, ex := grantReplacements[gt]
if !ex {
return gp.Privileges
}

privilegeSet := make(map[GrantPrivilege]struct{})

// Replace any shorthand privileges with their full equivalents
for _, p := range *gp {
if _, ok := grantReplacements[p]; ok {
for _, rp := range grantReplacements[p] {
for _, p := range gp.Privileges {
if _, ok := gr[p]; ok {
for _, rp := range gr[p] {
privilegeSet[rp] = struct{}{}
}
} else {
Expand Down Expand Up @@ -99,6 +250,15 @@ const (
GrantOptionGrant GrantOption = "GRANT"
)

type Routine struct {
// The name of the routine.
Name string `json:"name,omitempty"`

// The arguments of the routine.
// +optional
Arguments []string `json:"args,omitempty"`
}

// GrantParameters define the desired state of a PostgreSQL grant instance.
type GrantParameters struct {
// Privileges to be granted.
Expand Down Expand Up @@ -141,6 +301,20 @@ type GrantParameters struct {
// +optional
DatabaseSelector *xpv1.Selector `json:"databaseSelector,omitempty"`

// Schema this grant is for.
// +optional
Schema *string `json:"schema,omitempty"`

// SchemaRef references the schema object this grant it for.
// +immutable
// +optional
SchemaRef *xpv1.Reference `json:"schemaRef,omitempty"`

// SchemaSelector selects a reference to a Schema this grant is for.
// +immutable
// +optional
SchemaSelector *xpv1.Selector `json:"schemaSelector,omitempty"`

// MemberOf is the Role that this grant makes Role a member of.
// +optional
MemberOf *string `json:"memberOf,omitempty"`
Expand All @@ -154,6 +328,34 @@ type GrantParameters struct {
// +immutable
// +optional
MemberOfSelector *xpv1.Selector `json:"memberOfSelector,omitempty"`

// RevokePublicOnDb apply the statement "REVOKE ALL ON DATABASE %s FROM PUBLIC" to make database unreachable from public
// +optional
RevokePublicOnDb *bool `json:"revokePublicOnDb,omitempty" default:"false"`

// The columns upon which to grant the privileges.
// +optional
Columns []string `json:"columns,omitempty"`

// The tables upon which to grant the privileges.
// +optional
Tables []string `json:"tables,omitempty"`

// The sequences upon which to grant the privileges.
// +optional
Sequences []string `json:"sequences,omitempty"`

// The routines upon which to grant the privileges.
// +optional
Routines []Routine `json:"routines,omitempty"`

// The foreign data wrappers upon which to grant the privileges.
// +optional
ForeignDataWrappers []string `json:"foreignDataWrappers,omitempty"`

// The foreign servers upon which to grant the privileges.
// +optional
ForeignServers []string `json:"foreignServers,omitempty"`
}

// A GrantStatus represents the observed state of a Grant.
Expand Down Expand Up @@ -208,6 +410,20 @@ func (mg *Grant) ResolveReferences(ctx context.Context, c client.Reader) error {
mg.Spec.ForProvider.Database = reference.ToPtrValue(rsp.ResolvedValue)
mg.Spec.ForProvider.DatabaseRef = rsp.ResolvedReference

// Resolve spec.forProvider.schema
rsp, err = r.Resolve(ctx, reference.ResolutionRequest{
CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Schema),
Reference: mg.Spec.ForProvider.SchemaRef,
Selector: mg.Spec.ForProvider.SchemaSelector,
To: reference.To{Managed: &Schema{}, List: &SchemaList{}},
Extract: reference.ExternalName(),
})
if err != nil {
return errors.Wrap(err, "spec.forProvider.schema")
}
mg.Spec.ForProvider.Schema = reference.ToPtrValue(rsp.ResolvedValue)
mg.Spec.ForProvider.SchemaRef = rsp.ResolvedReference

// Resolve spec.forProvider.role
rsp, err = r.Resolve(ctx, reference.ResolutionRequest{
CurrentValue: reference.FromPtrValue(mg.Spec.ForProvider.Role),
Expand Down
Loading
Loading