handle revocations for roles that have privileges on sequences

This commit is contained in:
Mick Hansen 2016-06-30 12:53:34 +02:00
parent fcb0b580ab
commit c25788e1d4
2 changed files with 163 additions and 7 deletions

View file

@ -131,7 +131,7 @@ func TestBackend_basic(t *testing.T) {
Backend: b,
Steps: []logicaltest.TestStep{
testAccStepConfig(t, connData, false),
testAccStepRole(t),
testAccStepCreateRole(t, "web", testRole),
testAccStepReadCreds(t, b, config.StorageView, "web", connURL),
},
})
@ -157,7 +157,7 @@ func TestBackend_roleCrud(t *testing.T) {
Backend: b,
Steps: []logicaltest.TestStep{
testAccStepConfig(t, connData, false),
testAccStepRole(t),
testAccStepCreateRole(t, "web", testRole),
testAccStepReadRole(t, "web", testRole),
testAccStepDeleteRole(t, "web"),
testAccStepReadRole(t, "web", ""),
@ -165,6 +165,41 @@ func TestBackend_roleCrud(t *testing.T) {
})
}
func TestBackend_roleReadOnly(t *testing.T) {
config := logical.TestBackendConfig()
config.StorageView = &logical.InmemStorage{}
b, err := Factory(config)
if err != nil {
t.Fatal(err)
}
cid, connURL := prepareTestContainer(t, config.StorageView, b)
if cid != "" {
defer cleanupTestContainer(t, cid)
}
connData := map[string]interface{}{
"connection_url": connURL,
}
logicaltest.Test(t, logicaltest.TestCase{
AcceptanceTest: true,
PreCheck: func() { testAccPreCheck(t) },
Backend: b,
Steps: []logicaltest.TestStep{
testAccStepConfig(t, connData, false),
testAccStepCreateRole(t, "web", testRole),
testAccStepCreateRole(t, "web-readonly", testReadOnlyRole),
testAccStepReadRole(t, "web-readonly", testReadOnlyRole),
testAccStepCreateTable(t, b, "web"),
testAccStepReadCreds(t, b, "web-readonly"),
testAccStepDropTable(t, b, "web"),
testAccStepDeleteRole(t, "web-readonly"),
testAccStepDeleteRole(t, "web"),
testAccStepReadRole(t, "web-readonly", ""),
},
})
}
func testAccStepConfig(t *testing.T, d map[string]interface{}, expectError bool) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
@ -194,12 +229,12 @@ func testAccStepConfig(t *testing.T, d map[string]interface{}, expectError bool)
}
}
func testAccStepRole(t *testing.T) logicaltest.TestStep {
func testAccStepCreateRole(t *testing.T, n string, sql string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.UpdateOperation,
Path: "roles/web",
Path: "roles/" + n,
Data: map[string]interface{}{
"sql": testRole,
"sql": sql,
},
}
}
@ -226,6 +261,7 @@ func testAccStepReadCreds(t *testing.T, b logical.Backend, s logical.Storage, na
log.Printf("[WARN] Generated credentials: %v", d)
conn, err := pq.ParseURL(connURL)
if err != nil {
t.Fatal(err)
}
@ -258,7 +294,7 @@ func testAccStepReadCreds(t *testing.T, b logical.Backend, s logical.Storage, na
}
userRows := returnedRows()
if userRows != 2 {
if userRows < 2 {
t.Fatalf("did not get expected number of rows, got %d", userRows)
}
@ -292,6 +328,112 @@ func testAccStepReadCreds(t *testing.T, b logical.Backend, s logical.Storage, na
}
}
func testAccStepCreateTable(t *testing.T, b logical.Backend, name string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ReadOperation,
Path: "creds/" + name,
Check: func(resp *logical.Response) error {
var d struct {
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
}
if err := mapstructure.Decode(resp.Data, &d); err != nil {
return err
}
log.Printf("[WARN] Generated credentials: %v", d)
conn, err := pq.ParseURL(os.Getenv("PG_URL"))
if err != nil {
t.Fatal(err)
}
conn += " timezone=utc"
db, err := sql.Open("postgres", conn)
if err != nil {
t.Fatal(err)
}
_, err = db.Exec("CREATE TABLE test (id SERIAL PRIMARY KEY);")
if err != nil {
t.Fatal(err)
}
resp, err = b.HandleRequest(&logical.Request{
Operation: logical.RevokeOperation,
Secret: &logical.Secret{
InternalData: map[string]interface{}{
"secret_type": "creds",
"username": d.Username,
},
},
})
if err != nil {
return err
}
if resp != nil {
if resp.IsError() {
return fmt.Errorf("Error on resp: %#v", *resp)
}
}
return nil
},
}
}
func testAccStepDropTable(t *testing.T, b logical.Backend, name string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ReadOperation,
Path: "creds/" + name,
Check: func(resp *logical.Response) error {
var d struct {
Username string `mapstructure:"username"`
Password string `mapstructure:"password"`
}
if err := mapstructure.Decode(resp.Data, &d); err != nil {
return err
}
log.Printf("[WARN] Generated credentials: %v", d)
conn, err := pq.ParseURL(os.Getenv("PG_URL"))
if err != nil {
t.Fatal(err)
}
conn += " timezone=utc"
db, err := sql.Open("postgres", conn)
if err != nil {
t.Fatal(err)
}
_, err = db.Exec("DROP TABLE test;")
if err != nil {
t.Fatal(err)
}
resp, err = b.HandleRequest(&logical.Request{
Operation: logical.RevokeOperation,
Secret: &logical.Secret{
InternalData: map[string]interface{}{
"secret_type": "creds",
"username": d.Username,
},
},
})
if err != nil {
return err
}
if resp != nil {
if resp.IsError() {
return fmt.Errorf("Error on resp: %#v", *resp)
}
}
return nil
},
}
}
func testAccStepReadRole(t *testing.T, name string, sql string) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.ReadOperation,
@ -328,3 +470,12 @@ CREATE ROLE "{{name}}" WITH
VALID UNTIL '{{expiration}}';
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "{{name}}";
`
const testReadOnlyRole = `
CREATE ROLE "{{name}}" WITH
LOGIN
PASSWORD '{{password}}'
VALID UNTIL '{{expiration}}';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO "{{name}}";
GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO "{{name}}";
`

View file

@ -91,6 +91,7 @@ func (b *backend) secretCredsRevoke(
}
username, ok := usernameRaw.(string)
// Get our connection
db, err := b.DB(req.Storage)
if err != nil {
@ -150,7 +151,11 @@ func (b *backend) secretCredsRevoke(
pq.QuoteIdentifier(username)))
revocationStmts = append(revocationStmts, fmt.Sprintf(
`REVOKE USAGE ON SCHEMA public FROM %s;`,
"REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public FROM %s;",
pq.QuoteIdentifier(username)))
revocationStmts = append(revocationStmts, fmt.Sprintf(
"REVOKE USAGE ON SCHEMA public FROM %s;",
pq.QuoteIdentifier(username)))
// get the current database name so we can issue a REVOKE CONNECT for