diff --git a/infra/pkg/dns/dns.go b/infra/pkg/dns/dns.go index e3e0c67..bc7911f 100644 --- a/infra/pkg/dns/dns.go +++ b/infra/pkg/dns/dns.go @@ -8,6 +8,7 @@ package dns import ( "github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/compute" "github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/dns" + "github.com/pulumi/pulumi-gcp/sdk/v9/go/gcp/projects" "github.com/pulumi/pulumi/sdk/v3/go/pulumi" "gitea-infra/pkg/config" @@ -60,5 +61,19 @@ func New( return nil, err } + // The zone-scoped grant is not enough on its own: the googleclouddns plugin + // maps the domain to a zone by LISTING the project's managed zones, and a + // list is a project-level permission that no zone binding can confer. Without + // this, presenting the challenge fails with a bare 403. dns.reader adds + // read-only access and nothing else, and every write stays scoped to the zone + // above. + if _, err := projects.NewIAMMember(ctx, "gitea-vm-dns-reader", &projects.IAMMemberArgs{ + Project: pulumi.String(cfg.Project), + Role: pulumi.String("roles/dns.reader"), + Member: pulumi.Sprintf("serviceAccount:%s", vmServiceAccountEmail), + }, pulumi.DependsOn(deps)); err != nil { + return nil, err + } + return &DNS{Zone: zone, Record: rec}, nil } diff --git a/vm/config/Caddyfile.tmpl b/vm/config/Caddyfile.tmpl index 855218e..8b160fa 100644 --- a/vm/config/Caddyfile.tmpl +++ b/vm/config/Caddyfile.tmpl @@ -17,7 +17,8 @@ ${DOMAIN} { tls { dns googleclouddns { # Application Default Credentials come from the GCE metadata server. - # The VM service account holds roles/dns.admin scoped to this zone only. + # The VM service account holds roles/dns.admin scoped to this zone, plus + # project-level dns.reader so the plugin can list zones to find it. gcp_project {env.GCP_PROJECT} } # Only used for propagation checks. If issuance stalls waiting for