-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathpassword.go
131 lines (110 loc) · 3.45 KB
/
password.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package postgres
import (
"bytes"
"context"
"encoding/base64"
"fmt"
"io"
"os"
"os/exec"
"strings"
"time"
"github.com/nais/liberator/pkg/keygen"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func RotatePassword(ctx context.Context, appName, cluster, namespace, database string) error {
dbInfo, err := NewDBInfo(appName, namespace, cluster, database)
if err != nil {
return err
}
projectID, err := dbInfo.ProjectID(ctx)
if err != nil {
return err
}
dbConnectionInfo, err := dbInfo.DBConnection(ctx)
if err != nil {
return err
}
fmt.Println("Grant user cloudsql.admin access for 5 minutes")
err = grantUserAccess(ctx, projectID, "roles/cloudsql.admin", 5*time.Minute)
if err != nil {
return err
}
fmt.Println("Generating new password")
newPassword, err := generatePassword()
if err != nil {
return err
}
dbConnectionInfo.SetPassword(newPassword)
fmt.Printf("Rotating password for user %v in database %v\n", dbConnectionInfo.username, dbConnectionInfo.dbName)
err = rotatePasswordForDatabaseUser(ctx, projectID, dbConnectionInfo.instance, dbConnectionInfo.username, dbConnectionInfo.password)
if err != nil {
return err
}
fmt.Printf("Updating password in k8s secret google-sql-%v\n", dbInfo.appName)
err = updateKubernetesSecret(ctx, dbInfo, dbConnectionInfo)
if err != nil {
return err
}
fmt.Println("Password rotated")
return nil
}
func updateKubernetesSecret(ctx context.Context, dbInfo *DBInfo, dbConnectionInfo *ConnectionInfo) error {
secret, err := dbInfo.k8sClient.CoreV1().Secrets(dbInfo.namespace).Get(ctx, "google-sql-"+dbInfo.appName, v1.GetOptions{})
if err != nil {
return fmt.Errorf("unable to the k8s secret %q in %q: %w", "google-sql-"+dbInfo.appName, dbInfo.namespace, err)
}
jdbcUrlSet := false
prefix := ""
for key := range secret.Data {
if strings.HasSuffix(key, "_PASSWORD") {
secret.Data[key] = []byte(dbConnectionInfo.password)
}
if strings.HasSuffix(key, "_URL") {
if strings.HasSuffix(key, "_JDBC_URL") && dbConnectionInfo.jdbcUrl != nil {
secret.Data[key] = []byte(dbConnectionInfo.jdbcUrl.String())
jdbcUrlSet = true
} else if dbConnectionInfo.url != nil {
secret.Data[key] = []byte(dbConnectionInfo.url.String())
prefix = strings.TrimSuffix(key, "_URL")
}
}
}
if !jdbcUrlSet && dbConnectionInfo.jdbcUrl != nil && len(prefix) > 0 {
key := prefix + "_JDBC_URL"
secret.Data[key] = []byte(dbConnectionInfo.jdbcUrl.String())
}
_, err = dbInfo.k8sClient.CoreV1().Secrets(dbInfo.namespace).Update(ctx, secret, v1.UpdateOptions{})
if err != nil {
return fmt.Errorf("failed updating k8s secret %q in %q with new password: %w", "google-sql-"+dbInfo.appName, dbInfo.namespace, err)
}
return nil
}
func rotatePasswordForDatabaseUser(ctx context.Context, projectID, instance, username, password string) error {
args := []string{
"sql",
"users",
"set-password",
username,
"--password", password,
"--instance", strings.Split(instance, ":")[2],
"--project", projectID,
}
buf := &bytes.Buffer{}
cmd := exec.CommandContext(ctx, "gcloud", args...)
cmd.Stdout = buf
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
io.Copy(os.Stdout, buf)
return fmt.Errorf("error running gcloud command: %w", err)
}
return nil
}
func generatePassword() (string, error) {
key, err := keygen.Keygen(32)
if err != nil {
return "", fmt.Errorf("unable to generate secret for sql user: %s", err)
}
return base64.URLEncoding.WithPadding(base64.NoPadding).EncodeToString(key), nil
}