Skip to content

Commit

Permalink
Ensure we're only creating a single policy binding for each role
Browse files Browse the repository at this point in the history
Co-authored-by: Erik Vattekar <erik.vattekar@nav.no>
  • Loading branch information
thokra-nav and erikvatt committed Jan 31, 2022
1 parent 76d3da8 commit fb6fe30
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 68 deletions.
40 changes: 3 additions & 37 deletions cmd/root/postgres/grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package postgres

import (
"context"
"fmt"
"io"
"os"
"os/exec"
Expand Down Expand Up @@ -38,10 +39,11 @@ var grantCmd = &cobra.Command{
return err
}

if err := grantAccess(ctx, projectID, 1*time.Hour); err != nil {
if err := grantUserAccess(ctx, projectID, "roles/cloudsql.admin", 5*time.Minute); err != nil {
return err
}
if err := createSQLUser(ctx, projectID, connectionName); err != nil {
fmt.Fprintln(os.Stderr, "Error creating SQL user. One might already exist.")
return err
}

Expand Down Expand Up @@ -70,39 +72,3 @@ func createSQLUser(ctx context.Context, projectID, instance string) error {
cmd.Stderr = os.Stderr
return cmd.Run()
}

func grantAccess(ctx context.Context, projectID string, duration time.Duration) error {
email, err := currentEmail(ctx)
if err != nil {
return err
}

args := []string{
"projects",
"add-iam-policy-binding",
projectID,
"--member", "user:" + email,
"--role", "roles/cloudsql.admin",
}

if duration > 0 {
timestamp := time.Now().Add(duration).UTC().Format(time.RFC3339)
args = append(args,
"--condition",
"expression=request.time < timestamp('"+timestamp+"'),title=temp_access",
)
}
cmd := exec.CommandContext(ctx, "gcloud", args...)
cmd.Stdout = io.Discard
cmd.Stderr = os.Stderr
return cmd.Run()
}

func currentEmail(ctx context.Context) (string, error) {
cmd := exec.CommandContext(ctx, "gcloud", "config", "get-value", "account")
out, err := cmd.Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(out)), nil
}
125 changes: 125 additions & 0 deletions cmd/root/postgres/iam.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package postgres

import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"strings"
"time"
)

func currentEmail(ctx context.Context) (string, error) {
cmd := exec.CommandContext(ctx, "gcloud", "config", "get-value", "account")
out, err := cmd.Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(out)), nil
}

func grantUserAccess(ctx context.Context, projectID, role string, duration time.Duration) error {
email, err := currentEmail(ctx)
if err != nil {
return err
}

exists, err := cleanupPermissions(ctx, projectID, email, role, "nais_cli_access")
if err != nil {
return err
}
if exists {
fmt.Println("User already has permanent access to database")
return nil
}

args := []string{
"projects",
"add-iam-policy-binding",
projectID,
"--member", "user:" + email,
"--role", role,
}

if duration > 0 {
timestamp := time.Now().Add(duration).UTC().Format(time.RFC3339)
args = append(args,
"--condition",
formatCondition("request.time < timestamp('"+timestamp+"')", "nais_cli_access"),
)
}
cmd := exec.CommandContext(ctx, "gcloud", args...)
cmd.Stdout = io.Discard
cmd.Stderr = os.Stderr
return cmd.Run()
}

func cleanupPermissions(ctx context.Context, projectID, email, role, conditionName string) (exists bool, err error) {
args := []string{
"projects",
"get-iam-policy",
projectID,
"--format", "json",
}
cmd := exec.CommandContext(ctx, "gcloud", args...)
out, err := cmd.Output()
if err != nil {
return false, err
}
bindings := &policyBindings{}
if err := json.Unmarshal(out, bindings); err != nil {
return false, err
}

expr := ""
OUTER:
for _, binding := range bindings.Bindings {
if binding.Role == role {
for _, member := range binding.Members {
if member == "user:"+email {
if binding.Condition == nil {
return true, nil
}
if binding.Condition.Title == conditionName {
expr = formatCondition(binding.Condition.Expression, binding.Condition.Title)
break OUTER
}
}
}
}
}

if expr == "" {
return false, nil
}

args = []string{
"projects",
"remove-iam-policy-binding",
projectID,
"--member", "user:" + email,
"--role", role,
"--condition", expr,
}
cmd = exec.CommandContext(ctx, "gcloud", args...)
cmd.Stdout = io.Discard
cmd.Stderr = os.Stderr
return false, cmd.Run()
}

type policyBindings struct {
Bindings []struct {
Role string `json:"role"`
Members []string `json:"members"`
Condition *struct {
Title string `json:"title"`
Expression string `json:"expression"`
} `json:"condition"`
} `json:"bindings"`
}

func formatCondition(expr, title string) string {
return fmt.Sprintf("expression=%v,title=%v", expr, title)
}
32 changes: 1 addition & 31 deletions cmd/root/postgres/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@ package postgres
import (
"context"
"fmt"
"io"
"log"
"net"
"os"
"os/exec"
"os/signal"
"syscall"
"time"
Expand Down Expand Up @@ -54,7 +51,7 @@ var proxyCmd = &cobra.Command{
}

func runProxy(ctx context.Context, projectID, connectionName, address string, port chan int) error {
if err := grantUserAccess(ctx, projectID, 1*time.Hour); err != nil {
if err := grantUserAccess(ctx, projectID, "roles/cloudsql.instanceUser", 1*time.Hour); err != nil {
return err
}

Expand Down Expand Up @@ -126,30 +123,3 @@ func runProxy(ctx context.Context, projectID, connectionName, address string, po

return nil
}

func grantUserAccess(ctx context.Context, projectID string, duration time.Duration) error {
email, err := currentEmail(ctx)
if err != nil {
return err
}

args := []string{
"projects",
"add-iam-policy-binding",
projectID,
"--member", "user:" + email,
"--role", "roles/cloudsql.instanceUser",
}

if duration > 0 {
timestamp := time.Now().Add(duration).UTC().Format(time.RFC3339)
args = append(args,
"--condition",
"expression=request.time < timestamp('"+timestamp+"'),title=temp_access",
)
}
cmd := exec.CommandContext(ctx, "gcloud", args...)
cmd.Stdout = io.Discard
cmd.Stderr = os.Stderr
return cmd.Run()
}

0 comments on commit fb6fe30

Please sign in to comment.