Skip to content

Commit

Permalink
Execute before and after create hook
Browse files Browse the repository at this point in the history
  • Loading branch information
bersace committed May 14, 2024
1 parent 69a3c13 commit 0633188
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 25 deletions.
1 change: 1 addition & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pages](https://github.com/dalibo/ldap2pg/pulls?utf8=%E2%9C%93&q=is%3Apr%20is%3Am
- Load .env file.
- Watch time spent inspecting Postgres.
- Log Postgres out of band warnings.
- Execute arbitrary SQL snippet before and after role creation.
- Escape attribute with : `{sAMAccountName.identifier()}` and `{sAMAccountName.string()}`.


Expand Down
30 changes: 30 additions & 0 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,36 @@ rules:
```


#### `before_create` { #role-before-create }

SQL snippet to execute before role creation.
`before_create` accepts LDAP attributes injection using curly braces.
You are responsible to escape attribute with either `.identifier()` or `.string()`.

``` yaml
rules:
- ldapsearch: ...
role:
name: "{cn}"
before_create: "INSERT INTO log VALUES ({cn.string()})"
```


#### `after_create` { #role-after-create }

SQL snippet to execute after role creation.
`after_create` accepts LDAP attributes injection using curly braces.
You are responsible to escape attribute with either `.identifier()` or `.string()`.

``` yaml
rules:
- ldapsearch: ...
role:
name: "{sAMAccountName}"
after_create: "CREATE SCHEMA {sAMAccountName.identifier()} AUTHORIZATION {sAMAccountName.identifier()}"
```


### `grant` { #rules-grant }

[grant rule]: #rules-grant
Expand Down
2 changes: 1 addition & 1 deletion internal/config/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func NormalizeRoleRule(yaml interface{}) (rule map[string]interface{}, err error
return nil, fmt.Errorf("bad type: %T", yaml)
}

err = CheckSpuriousKeys(&rule, "names", "comment", "parents", "options", "config")
err = CheckSpuriousKeys(&rule, "names", "comment", "parents", "options", "config", "before_create", "after_create")
return
}

Expand Down
41 changes: 29 additions & 12 deletions internal/role/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import (
)

type Role struct {
Name string
Comment string
Parents []Membership
Options Options
Config *Config
Name string
Comment string
Parents []Membership
Options Options
Config *Config
BeforeCreate string
AfterCreate string
}

func New() Role {
Expand Down Expand Up @@ -182,6 +184,14 @@ func (r *Role) Alter(wanted Role) (out []postgres.SyncQuery) {
func (r *Role) Create() (out []postgres.SyncQuery) {
identifier := pgx.Identifier{r.Name}

if r.BeforeCreate != "" {
out = append(out, postgres.SyncQuery{
Description: "Run before create hook.",
LogArgs: []interface{}{"role", r.Name, "sql", r.BeforeCreate},
Query: r.BeforeCreate,
})
}

if len(r.Parents) > 0 {
parents := []interface{}{}
for _, parent := range r.Parents {
Expand Down Expand Up @@ -211,18 +221,25 @@ func (r *Role) Create() (out []postgres.SyncQuery) {
QueryArgs: []interface{}{identifier, r.Comment},
})

if nil == r.Config {
return
if r.Config != nil {
for k, v := range *r.Config {
out = append(out, postgres.SyncQuery{
Description: "Set role config.",
LogArgs: []interface{}{"role", r.Name, "config", k, "value", v},
Query: `ALTER ROLE %s SET %s TO %s`,
QueryArgs: []interface{}{identifier, pgx.Identifier{k}, v},
})
}
}

for k, v := range *r.Config {
if r.AfterCreate != "" {
out = append(out, postgres.SyncQuery{
Description: "Set role config.",
LogArgs: []interface{}{"role", r.Name, "config", k, "value", v},
Query: `ALTER ROLE %s SET %s TO %s`,
QueryArgs: []interface{}{identifier, pgx.Identifier{k}, v},
Description: "Run after create hook.",
LogArgs: []interface{}{"role", r.Name, "sql", r.AfterCreate},
Query: r.AfterCreate,
})
}

return
}

Expand Down
30 changes: 18 additions & 12 deletions internal/wanted/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,21 @@ func (r GrantRule) Generate(results *ldap.Result, privileges privilege.RefMap) <
}

type RoleRule struct {
Name pyfmt.Format
Options role.Options
Comment pyfmt.Format
Parents []MembershipRule
Config *role.Config
Name pyfmt.Format
Options role.Options
Comment pyfmt.Format
Parents []MembershipRule
Config *role.Config
BeforeCreate pyfmt.Format `mapstructure:"before_create"`
AfterCreate pyfmt.Format `mapstructure:"after_create"`
}

func (r RoleRule) IsStatic() bool {
return lists.And(r.Formats(), func(f pyfmt.Format) bool { return f.IsStatic() })
}

func (r RoleRule) Formats() []pyfmt.Format {
fmts := []pyfmt.Format{r.Name, r.Comment}
fmts := []pyfmt.Format{r.Name, r.Comment, r.BeforeCreate, r.AfterCreate}
for _, p := range r.Parents {
fmts = append(fmts, p.Name)
}
Expand All @@ -124,21 +126,25 @@ func (r RoleRule) Generate(results *ldap.Result) <-chan role.Role {
if nil == results.Entry {
// Case static rule.
role := role.Role{
Name: r.Name.String(),
Comment: r.Comment.String(),
Options: r.Options,
Parents: parents,
Config: r.Config,
Name: r.Name.String(),
Comment: r.Comment.String(),
Options: r.Options,
Parents: parents,
Config: r.Config,
BeforeCreate: r.BeforeCreate.String(),
AfterCreate: r.AfterCreate.String(),
}
ch <- role
} else {
// Case dynamic rule.
for values := range results.GenerateValues(r.Name, r.Comment) {
for values := range results.GenerateValues(r.Name, r.Comment, r.BeforeCreate, r.AfterCreate) {
role := role.Role{}
role.Name = r.Name.Format(values)
role.Comment = r.Comment.Format(values)
role.Options = r.Options
role.Parents = append(parents[0:0], parents...) // copy
role.BeforeCreate = r.BeforeCreate.Format(values)
role.AfterCreate = r.AfterCreate.Format(values)
ch <- role
}
}
Expand Down
10 changes: 10 additions & 0 deletions test/extra.ldap2pg.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,13 @@ rules:
comment: "group: {memberOf.sAMAccountName}"
options: LOGIN SUPERUSER
parents: [ldap_roles]

- description: "Hooks"
ldapsearch:
base: "cn=users,dc=bridoulou,dc=fr"
filter: "(cn=corinne)"
role:
name: "{cn}"
parent: ldap_roles
before_create: "CREATE SCHEMA {cn.identifier()};"
after_create: "CREATE TABLE {cn.identifier()}.username AS SELECT {cn.string()}::regrole AS username;"
4 changes: 4 additions & 0 deletions test/test_extra.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,7 @@ def test_role_config(extrarun, psql):
'application_name': 'keep-me',
}
assert expected_unmodified_config == psql.config('nicolas')


def test_role_hook(extrarun, psql):
assert psql.scalar("SELECT username FROM corinne.username;") == "corinne"

0 comments on commit 0633188

Please sign in to comment.