mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 15:51:43 +00:00
msp: support custom project-level iam config (#57905)
This commit is contained in:
parent
cc4f7ee9b6
commit
d334b2a2c0
@ -29,11 +29,14 @@ go_library(
|
||||
"//lib/errors",
|
||||
"//lib/pointers",
|
||||
"@com_github_aws_constructs_go_constructs_v10//:constructs",
|
||||
"@com_github_aws_jsii_runtime_go//:jsii-runtime-go",
|
||||
"@com_github_grafana_regexp//:regexp",
|
||||
"@com_github_hashicorp_terraform_cdk_go_cdktf//:cdktf",
|
||||
"@com_github_sourcegraph_managed_services_platform_cdktf_gen_google//cloudrunv2service",
|
||||
"@com_github_sourcegraph_managed_services_platform_cdktf_gen_google//cloudrunv2serviceiammember",
|
||||
"@com_github_sourcegraph_managed_services_platform_cdktf_gen_google//computenetwork",
|
||||
"@com_github_sourcegraph_managed_services_platform_cdktf_gen_google//computesubnetwork",
|
||||
"@com_github_sourcegraph_managed_services_platform_cdktf_gen_google//projectiamcustomrole",
|
||||
"@com_github_sourcegraph_managed_services_platform_cdktf_gen_google//vpcaccessconnector",
|
||||
"@org_golang_x_exp//maps",
|
||||
"@org_golang_x_exp//slices",
|
||||
|
||||
@ -4,9 +4,12 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/jsii-runtime-go"
|
||||
"github.com/grafana/regexp"
|
||||
"github.com/hashicorp/terraform-cdk-go/cdktf"
|
||||
"github.com/sourcegraph/managed-services-platform-cdktf/gen/google/cloudrunv2service"
|
||||
"github.com/sourcegraph/managed-services-platform-cdktf/gen/google/cloudrunv2serviceiammember"
|
||||
"github.com/sourcegraph/managed-services-platform-cdktf/gen/google/projectiamcustomrole"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/dev/managedservicesplatform/googlesecretsmanager"
|
||||
"github.com/sourcegraph/sourcegraph/dev/managedservicesplatform/internal/resource/bigquery"
|
||||
@ -117,16 +120,46 @@ func NewStack(stacks *stack.Set, vars Variables) (*Output, error) {
|
||||
ByteLength: 8,
|
||||
})
|
||||
|
||||
id := resourceid.New("cloudrun")
|
||||
|
||||
var customRole projectiamcustomrole.ProjectIamCustomRole
|
||||
if vars.Service.IAM != nil && len(vars.Service.IAM.Permissions) > 0 {
|
||||
customRole = projectiamcustomrole.NewProjectIamCustomRole(stack, id.ResourceID("custom-role"), &projectiamcustomrole.ProjectIamCustomRoleConfig{
|
||||
RoleId: pointers.Ptr(fmt.Sprintf("%s_custom_role", id.DisplayName())),
|
||||
Title: pointers.Ptr(fmt.Sprintf("%s custom role", id.DisplayName())),
|
||||
Project: &vars.ProjectID,
|
||||
Permissions: jsii.Strings(vars.Service.IAM.Permissions...),
|
||||
})
|
||||
}
|
||||
|
||||
// Set up configuration for the core Cloud Run service
|
||||
cloudRun := &cloudRunServiceBuilder{
|
||||
ServiceAccount: serviceaccount.New(stack,
|
||||
resourceid.New("cloudrun"),
|
||||
id,
|
||||
serviceaccount.Config{
|
||||
ProjectID: vars.ProjectID,
|
||||
AccountID: fmt.Sprintf("%s-sa", vars.Service.ID),
|
||||
DisplayName: fmt.Sprintf("%s Service Account",
|
||||
pointers.Deref(vars.Service.Name, vars.Service.ID)),
|
||||
Roles: serviceAccountRoles,
|
||||
Roles: func() []serviceaccount.Role {
|
||||
if vars.Service.IAM != nil && len(vars.Service.IAM.Roles) > 0 {
|
||||
var rs []serviceaccount.Role
|
||||
for _, r := range vars.Service.IAM.Roles {
|
||||
rs = append(rs, serviceaccount.Role{
|
||||
ID: matchNonAlphaNumericRegex.ReplaceAllString(r, "_"),
|
||||
Role: r,
|
||||
})
|
||||
}
|
||||
serviceAccountRoles = append(rs, serviceAccountRoles...)
|
||||
}
|
||||
if customRole != nil {
|
||||
serviceAccountRoles = append(serviceAccountRoles, serviceaccount.Role{
|
||||
ID: "role_cloudrun_custom_role",
|
||||
Role: *customRole.Name(),
|
||||
})
|
||||
}
|
||||
return serviceAccountRoles
|
||||
}(),
|
||||
}),
|
||||
|
||||
DiagnosticsSecret: diagnosticsSecret,
|
||||
@ -451,3 +484,7 @@ func (c cloudRunServiceBuilder) Build(stack cdktf.TerraformStack, vars Variables
|
||||
Volumes: c.AdditionalVolumes,
|
||||
}}), nil
|
||||
}
|
||||
|
||||
var (
|
||||
matchNonAlphaNumericRegex = regexp.MustCompile("[^a-zA-Z0-9]+")
|
||||
)
|
||||
|
||||
@ -83,6 +83,9 @@ type Variables struct {
|
||||
// EnableAuditLogs ships GCP audit logs to security cluster.
|
||||
// TODO: Not yet implemented
|
||||
EnableAuditLogs bool
|
||||
|
||||
// Services is a list of additional GCP services to enable.
|
||||
Services []string
|
||||
}
|
||||
|
||||
const StackName = "project"
|
||||
@ -144,7 +147,7 @@ func NewStack(stacks *stack.Set, vars Variables) (*Output, error) {
|
||||
}),
|
||||
}
|
||||
|
||||
for _, service := range gcpServices {
|
||||
for _, service := range append(gcpServices, vars.Services...) {
|
||||
projectservice.NewProjectService(stack,
|
||||
id.ResourceID("project-service-%s", strings.ReplaceAll(service, ".", "-")),
|
||||
&projectservice.ProjectServiceConfig{
|
||||
|
||||
@ -84,6 +84,12 @@ func (r *Renderer) RenderEnvironment(
|
||||
"environment": env.ID,
|
||||
"msp": "true",
|
||||
},
|
||||
Services: func() []string {
|
||||
if svc.IAM != nil && len(svc.IAM.Services) > 0 {
|
||||
return svc.IAM.Services
|
||||
}
|
||||
return nil
|
||||
}(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create project stack")
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
load("//dev:go_defs.bzl", "go_test")
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
@ -12,6 +13,14 @@ go_library(
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//lib/errors",
|
||||
"@com_github_grafana_regexp//:regexp",
|
||||
"@io_k8s_sigs_yaml//:yaml",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "spec_test",
|
||||
srcs = ["service_test.go"],
|
||||
embed = [":spec"],
|
||||
deps = ["@com_github_stretchr_testify//assert"],
|
||||
)
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
package spec
|
||||
|
||||
import "github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
import (
|
||||
"github.com/grafana/regexp"
|
||||
|
||||
"github.com/sourcegraph/sourcegraph/lib/errors"
|
||||
)
|
||||
|
||||
type ServiceSpec struct {
|
||||
// ID is an all-lowercase, hyphen-delimited identifier for the service,
|
||||
@ -25,6 +29,10 @@ type ServiceSpec struct {
|
||||
// ProjectIDSuffixLength can be configured to truncate the length of the
|
||||
// service's generated project IDs.
|
||||
ProjectIDSuffixLength *int `json:"projectIDSuffixLength,omitempty"`
|
||||
|
||||
// IAM is an optional IAM configuration for the service account on the
|
||||
// service's GCP project.
|
||||
IAM *ServiceIAMSpec `json:"iam,omitempty"`
|
||||
}
|
||||
|
||||
func (s ServiceSpec) Validate() []error {
|
||||
@ -34,6 +42,10 @@ func (s ServiceSpec) Validate() []error {
|
||||
errs = append(errs, errors.New("projectIDSuffixLength must be >= 4"))
|
||||
}
|
||||
|
||||
if s.IAM != nil {
|
||||
errs = append(errs, s.IAM.Validate()...)
|
||||
}
|
||||
|
||||
// TODO: Add validation
|
||||
return errs
|
||||
}
|
||||
@ -41,3 +53,37 @@ func (s ServiceSpec) Validate() []error {
|
||||
type Protocol string
|
||||
|
||||
const ProtocolH2C Protocol = "h2c"
|
||||
|
||||
type ServiceIAMSpec struct {
|
||||
// Services is a list of GCP services to enable in the service's project.
|
||||
Services []string `json:"services,omitempty"`
|
||||
|
||||
// Roles is a list of IAM roles to grant to the service account.
|
||||
Roles []string `json:"roles,omitempty"`
|
||||
// Permissions is a list of IAM permissions to grant to the service account.
|
||||
//
|
||||
// MSP will create a custom role with these permissions and grant it to the
|
||||
// service account.
|
||||
Permissions []string `json:"permissions,omitempty"`
|
||||
}
|
||||
|
||||
func (s ServiceIAMSpec) Validate() []error {
|
||||
var errs []error
|
||||
|
||||
for _, role := range s.Roles {
|
||||
if !validIAMRole(role) {
|
||||
errs = append(errs, errors.Errorf("invalid IAM role %q, must be one of custom role or predefined role", role))
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func validIAMRole(role string) bool {
|
||||
return matchCustomRole.MatchString(role) || matchPredefinedRole.MatchString(role)
|
||||
}
|
||||
|
||||
var (
|
||||
matchCustomRole = regexp.MustCompile(`^(projects|organizations)/[a-z0-9_-]+/roles/[a-zA-Z_\.]+$`)
|
||||
matchPredefinedRole = regexp.MustCompile(`^roles/[a-zA-Z\.]+$`)
|
||||
)
|
||||
|
||||
45
dev/managedservicesplatform/spec/service_test.go
Normal file
45
dev/managedservicesplatform/spec/service_test.go
Normal file
@ -0,0 +1,45 @@
|
||||
package spec
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestValidIAMRole(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
role string
|
||||
ok bool
|
||||
}{
|
||||
{
|
||||
name: "project custom role",
|
||||
role: "projects/project-id/roles/custom_role",
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "organization custom role",
|
||||
role: "organizations/org-id/roles/custom_role",
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "predefined role",
|
||||
role: "roles/iam.getIamPolicy",
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "invalid role",
|
||||
role: "invalid-role",
|
||||
},
|
||||
{
|
||||
name: "invalid role",
|
||||
role: "projects/project-id/roles/a-b-c",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.ok, validIAMRole(tt.role))
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -107,7 +107,8 @@ func init() {
|
||||
Disabled: pointers.Ptr(true),
|
||||
},
|
||||
Env: map[string]string{
|
||||
"SRC_LOG_LEVEL": "info",
|
||||
"SRC_LOG_LEVEL": "info",
|
||||
"SRC_LOG_FORMAT": "json_gcp",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user