github.com/navikt/knorten@v0.0.0-20240419132333-1333f46ed8b6/pkg/user/gcp.go (about) 1 package user 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "os/exec" 8 "strings" 9 10 "github.com/navikt/knorten/pkg/gcp" 11 ) 12 13 var gcpIAMPolicyBindingsRoles = []string{ 14 "roles/compute.viewer", 15 "roles/iap.tunnelResourceAccessor", 16 "roles/monitoring.viewer", 17 } 18 19 const opsServiceAccountEmail = "knada-vm-ops-agent@knada-gcp.iam.gserviceaccount.com" 20 21 func (c Client) createComputeInstanceInGCP(ctx context.Context, instanceName, email string) error { 22 if c.dryRun { 23 return nil 24 } 25 26 exists, err := c.computeInstanceExistsInGCP(ctx, instanceName) 27 if err != nil { 28 return err 29 } 30 31 if exists { 32 return nil 33 } 34 35 cmd := exec.CommandContext(ctx, 36 "gcloud", 37 "--quiet", 38 "compute", 39 "instances", 40 "create", 41 instanceName, 42 "--project", c.gcpProject, 43 "--zone", c.gcpZone, 44 "--machine-type", "n2-standard-2", 45 "--network-interface", "network=knada-vpc,subnet=knada,no-address", 46 fmt.Sprintf("--labels=goog-ops-agent-policy=v2-x86-template-1-2-0,created-by=knorten,user=%v", normalizeEmailToName(email)), 47 "--tags=knadavm", 48 "--metadata=block-project-ssh-keys=TRUE,enable-osconfig=TRUE", 49 "--service-account", opsServiceAccountEmail, 50 "--no-scopes", 51 ) 52 53 stdOut := &bytes.Buffer{} 54 stdErr := &bytes.Buffer{} 55 cmd.Stdout = stdOut 56 cmd.Stderr = stdErr 57 if err := cmd.Run(); err != nil { 58 return fmt.Errorf("%v\nstderr: %v", err, stdErr.String()) 59 } 60 61 return nil 62 } 63 64 func (c Client) resizeComputeInstanceDiskGCP(ctx context.Context, instanceName string, diskSize int32) error { 65 if c.dryRun { 66 return nil 67 } 68 69 exists, err := c.computeInstanceExistsInGCP(ctx, instanceName) 70 if err != nil { 71 return err 72 } 73 74 if !exists { 75 return nil 76 } 77 78 cmd := exec.CommandContext(ctx, 79 "gcloud", 80 "--quiet", 81 "compute", 82 "disks", 83 "resize", 84 instanceName, 85 fmt.Sprintf("--project=%v", c.gcpProject), 86 fmt.Sprintf("--zone=%v", c.gcpZone), 87 fmt.Sprintf("--size=%vGB", diskSize), 88 ) 89 90 stdOut := &bytes.Buffer{} 91 stdErr := &bytes.Buffer{} 92 cmd.Stdout = stdOut 93 cmd.Stderr = stdErr 94 if err := cmd.Run(); err != nil { 95 return fmt.Errorf("%v\nstderr: %v", err, stdErr.String()) 96 } 97 98 return nil 99 } 100 101 func (c Client) deleteComputeInstanceFromGCP(ctx context.Context, instanceName string) error { 102 if c.dryRun { 103 return nil 104 } 105 106 exists, err := c.computeInstanceExistsInGCP(ctx, instanceName) 107 if err != nil { 108 return err 109 } 110 111 if !exists { 112 return nil 113 } 114 115 cmd := exec.CommandContext(ctx, 116 "gcloud", 117 "--quiet", 118 "compute", 119 "instances", 120 "delete", 121 "--delete-disks=all", 122 instanceName, 123 "--zone", c.gcpZone, 124 "--project", c.gcpProject, 125 ) 126 127 stdOut := &bytes.Buffer{} 128 stdErr := &bytes.Buffer{} 129 cmd.Stdout = stdOut 130 cmd.Stderr = stdErr 131 if err := cmd.Run(); err != nil { 132 return fmt.Errorf("%v\nstderr: %v", err, stdErr.String()) 133 } 134 135 return nil 136 } 137 138 func (c Client) computeInstanceExistsInGCP(ctx context.Context, instanceName string) (bool, error) { 139 cmd := exec.CommandContext(ctx, 140 "gcloud", 141 "--quiet", 142 "compute", 143 "instances", 144 "list", 145 "--format=get(name)", 146 "--project", c.gcpProject, 147 fmt.Sprintf("--filter=name:%v", instanceName)) 148 149 stdOut := &bytes.Buffer{} 150 stdErr := &bytes.Buffer{} 151 cmd.Stdout = stdOut 152 cmd.Stderr = stdErr 153 if err := cmd.Run(); err != nil { 154 return false, fmt.Errorf("%v\nstderr: %v", err, stdErr.String()) 155 } 156 157 return stdOut.String() != "", nil 158 } 159 160 func (c Client) createIAMPolicyBindingsInGCP(ctx context.Context, instanceName, email string) error { 161 if c.dryRun { 162 return nil 163 } 164 165 if err := c.addComputeInstanceOwnerBindingInGCP(ctx, instanceName, email); err != nil { 166 return err 167 } 168 169 if err := c.addOpsServiceAccountUserBinding(ctx, email); err != nil { 170 return err 171 } 172 173 for _, role := range gcpIAMPolicyBindingsRoles { 174 if err := c.addProjectIAMPolicyBindingInGCP(ctx, instanceName, email, role); err != nil { 175 return err 176 } 177 } 178 179 return nil 180 } 181 182 func (c Client) deleteIAMPolicyBindingsFromGCP(ctx context.Context, instanceName, email string) error { 183 if c.dryRun { 184 return nil 185 } 186 187 if err := c.removeOpsServiceAccountUserBinding(ctx, email); err != nil { 188 return err 189 } 190 191 for _, role := range gcpIAMPolicyBindingsRoles { 192 if err := c.removeProjectIAMPolicyBindingFromGCP(ctx, instanceName, email, role); err != nil { 193 return err 194 } 195 } 196 197 return nil 198 } 199 200 func (c Client) addComputeInstanceOwnerBindingInGCP(ctx context.Context, instanceName, user string) error { 201 cmd := exec.CommandContext(ctx, 202 "gcloud", 203 "--quiet", 204 "compute", 205 "instances", 206 "add-iam-policy-binding", 207 instanceName, 208 "--zone", c.gcpZone, 209 "--project", c.gcpProject, 210 "--role", "roles/owner", 211 fmt.Sprintf("--member=user:%v", user), 212 ) 213 214 stdOut := &bytes.Buffer{} 215 stdErr := &bytes.Buffer{} 216 cmd.Stdout = stdOut 217 cmd.Stderr = stdErr 218 if err := cmd.Run(); err != nil { 219 return fmt.Errorf("%v\nstderr: %v", err, stdErr.String()) 220 } 221 222 return nil 223 } 224 225 func (c Client) addOpsServiceAccountUserBinding(ctx context.Context, email string) error { 226 cmd := exec.CommandContext(ctx, 227 "gcloud", 228 "--quiet", 229 "iam", 230 "service-accounts", 231 "add-iam-policy-binding", 232 opsServiceAccountEmail, 233 "--project", c.gcpProject, 234 "--role", "roles/iam.serviceAccountUser", 235 fmt.Sprintf("--member=user:%v", email), 236 ) 237 238 stdOut := &bytes.Buffer{} 239 stdErr := &bytes.Buffer{} 240 cmd.Stdout = stdOut 241 cmd.Stderr = stdErr 242 if err := cmd.Run(); err != nil { 243 return fmt.Errorf("%v\nstderr: %v", err, stdErr.String()) 244 } 245 246 return nil 247 } 248 249 func (c Client) removeOpsServiceAccountUserBinding(ctx context.Context, email string) error { 250 cmd := exec.CommandContext(ctx, 251 "gcloud", 252 "--quiet", 253 "iam", 254 "service-accounts", 255 "remove-iam-policy-binding", 256 opsServiceAccountEmail, 257 "--project", c.gcpProject, 258 "--role", "roles/iam.serviceAccountUser", 259 fmt.Sprintf("--member=user:%v", email), 260 ) 261 262 stdOut := &bytes.Buffer{} 263 stdErr := &bytes.Buffer{} 264 cmd.Stdout = stdOut 265 cmd.Stderr = stdErr 266 if err := cmd.Run(); err != nil { 267 return fmt.Errorf("%v\nstderr: %v", err, stdErr.String()) 268 } 269 270 return nil 271 } 272 273 func (c Client) addProjectIAMPolicyBindingInGCP(ctx context.Context, instanceName, user, role string) error { 274 if c.dryRun { 275 return nil 276 } 277 278 cmd := exec.CommandContext(ctx, 279 "gcloud", 280 "--quiet", 281 "projects", 282 "add-iam-policy-binding", 283 c.gcpProject, 284 "--role", role, 285 fmt.Sprintf("--member=user:%v", user), 286 ) 287 288 stdOut := &bytes.Buffer{} 289 stdErr := &bytes.Buffer{} 290 cmd.Stdout = stdOut 291 cmd.Stderr = stdErr 292 if err := cmd.Run(); err != nil { 293 return fmt.Errorf("%v\nstderr: %v", err, stdErr.String()) 294 } 295 296 return nil 297 } 298 299 func (c Client) removeProjectIAMPolicyBindingFromGCP(ctx context.Context, instanceName, user, role string) error { 300 if c.dryRun { 301 return nil 302 } 303 304 cmd := exec.CommandContext(ctx, 305 "gcloud", 306 "--quiet", 307 "projects", 308 "remove-iam-policy-binding", 309 c.gcpProject, 310 "--role", role, 311 fmt.Sprintf("--member=user:%v", user), 312 ) 313 314 stdOut := &bytes.Buffer{} 315 stdErr := &bytes.Buffer{} 316 cmd.Stdout = stdOut 317 cmd.Stderr = stdErr 318 if err := cmd.Run(); err != nil { 319 if strings.Contains(stdErr.String(), "not found") { 320 return nil 321 } 322 323 return fmt.Errorf("%v\nstderr: %v", err, stdErr.String()) 324 } 325 326 return nil 327 } 328 329 func normalizeEmailToName(email string) string { 330 name, _ := strings.CutSuffix(email, "@nav.no") 331 return strings.ReplaceAll(name, ".", "_") 332 } 333 334 func (c Client) createUserGSMInGCP(ctx context.Context, name, owner string) error { 335 if c.dryRun { 336 return nil 337 } 338 339 secret, err := gcp.CreateSecret(ctx, c.gcpProject, c.gcpRegion, name, map[string]string{"owner": name}) 340 if err != nil { 341 return err 342 } 343 344 if err := gcp.SetUsersSecretOwnerBinding(ctx, []string{owner}, secret.Name); err != nil { 345 return err 346 } 347 348 return nil 349 } 350 351 func (c Client) deleteUserGSMFromGCP(ctx context.Context, name string) error { 352 if c.dryRun { 353 return nil 354 } 355 356 if err := gcp.DeleteSecret(ctx, c.gcpProject, name); err != nil { 357 return err 358 } 359 360 return nil 361 }