From 66af4e6f1be7d870741299d7880bac1b207081c3 Mon Sep 17 00:00:00 2001 From: Modular Magician Date: Mon, 6 Feb 2023 22:14:34 +0000 Subject: [PATCH] add dns keys datasource (#7186) * initial commit for muxer and framework provider * minor changes to new provider and muxer * add provider model, provider schema, defaults in config * initial commit to upgrading the provider * match schema to sdk provider * remove duplicates * add dns record set, test and provider schema update * add dcl custom endpoints, custom endpoint validator and credentials validator * mv sdk provider defaults to config, fix up other small issues * add changes from merging with main * working with provider meta * move MultiEnvDefault function so it will hopefully be picked up by the validator * don't need to downgrade the provider * fix beta build issue * remove redundant dcl generated custom endpoint * strange org policy issue - only needed in ga * fix failures in beta * fix vcr tests * fix vcr tests for real hopefully * fix provider meta error * Update record set data source test to compare muxed provider to v4.50.0 This includes copying useful test util functions from terraform-provider-random (https://github.com/hashicorp/terraform-provider-random/blob/main/internal/provider/resource_integer_test.go#L1006) * Fix error in `providerVersion450` * add framework_test_utils * add functionality to configure the test framework provider and get check destroys working * add managed zone check destroy * add tflog to framework files * review comment changes * use version.ProviderVersion * add comment for test provider configure function * remove redundant Cloudbuildv2CustomEndpoint endpoint on framework provider * adding data source dns keys * fix providermeta errors * add sdk upgrade tests and check destroy to dns keys tests * add descriptions * formatting changes * add check attribute values equal * remove diffsuppress comment, add other test checks --------- Co-authored-by: Sarah French Signed-off-by: Modular Magician --- .changelog/7186.txt | 3 + google-beta/data_source_dns_key_test.go | 44 +- google-beta/data_source_dns_keys.go | 524 +++++++++++++++++------- google-beta/framework_provider.go | 1 + google-beta/framework_utils.go | 50 +++ google-beta/provider.go | 1 - 6 files changed, 455 insertions(+), 168 deletions(-) create mode 100644 .changelog/7186.txt diff --git a/.changelog/7186.txt b/.changelog/7186.txt new file mode 100644 index 0000000000..8ec013c069 --- /dev/null +++ b/.changelog/7186.txt @@ -0,0 +1,3 @@ +```release-note:none + +``` diff --git a/google-beta/data_source_dns_key_test.go b/google-beta/data_source_dns_key_test.go index 2c8e25403e..9e4e8a54ff 100644 --- a/google-beta/data_source_dns_key_test.go +++ b/google-beta/data_source_dns_key_test.go @@ -11,21 +11,41 @@ import ( func TestAccDataSourceDNSKeys_basic(t *testing.T) { t.Parallel() - dnsZoneName := fmt.Sprintf("data-dnskey-test-%s", randString(t, 10)) + dnsZoneName := fmt.Sprintf("tf-dnskey-test-%s", randString(t, 10)) + + var kskDigest1, kskDigest2, zskPubKey1, zskPubKey2, kskAlg1, kskAlg2 string vcrTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckDNSManagedZoneDestroyProducer(t), + CheckDestroy: testAccCheckDNSManagedZoneDestroyProducerFramework(t), Steps: []resource.TestStep{ { - Config: testAccDataSourceDNSKeysConfig(dnsZoneName, "on"), + ExternalProviders: providerVersion450(), + Config: testAccDataSourceDNSKeysConfig(dnsZoneName, "on"), Check: resource.ComposeTestCheckFunc( testAccDataSourceDNSKeysDSRecordCheck("data.google_dns_keys.foo_dns_key"), resource.TestCheckResourceAttr("data.google_dns_keys.foo_dns_key", "key_signing_keys.#", "1"), resource.TestCheckResourceAttr("data.google_dns_keys.foo_dns_key", "zone_signing_keys.#", "1"), resource.TestCheckResourceAttr("data.google_dns_keys.foo_dns_key_id", "key_signing_keys.#", "1"), resource.TestCheckResourceAttr("data.google_dns_keys.foo_dns_key_id", "zone_signing_keys.#", "1"), + testExtractResourceAttr("data.google_dns_keys.foo_dns_key", "key_signing_keys.0.digests.0.digest", &kskDigest1), + testExtractResourceAttr("data.google_dns_keys.foo_dns_key_id", "zone_signing_keys.0.public_key", &zskPubKey1), + testExtractResourceAttr("data.google_dns_keys.foo_dns_key_id", "key_signing_keys.0.algorithm", &kskAlg1), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(t), + Config: testAccDataSourceDNSKeysConfig(dnsZoneName, "on"), + Check: resource.ComposeTestCheckFunc( + testAccDataSourceDNSKeysDSRecordCheck("data.google_dns_keys.foo_dns_key"), + resource.TestCheckResourceAttr("data.google_dns_keys.foo_dns_key", "key_signing_keys.#", "1"), + resource.TestCheckResourceAttr("data.google_dns_keys.foo_dns_key", "zone_signing_keys.#", "1"), + testExtractResourceAttr("data.google_dns_keys.foo_dns_key", "key_signing_keys.0.digests.0.digest", &kskDigest2), + testExtractResourceAttr("data.google_dns_keys.foo_dns_key_id", "zone_signing_keys.0.public_key", &zskPubKey2), + testExtractResourceAttr("data.google_dns_keys.foo_dns_key_id", "key_signing_keys.0.algorithm", &kskAlg2), + testCheckAttributeValuesEqual(&kskDigest1, &kskDigest2), + testCheckAttributeValuesEqual(&zskPubKey1, &zskPubKey2), + testCheckAttributeValuesEqual(&kskAlg1, &kskAlg2), ), }, }, @@ -35,15 +55,23 @@ func TestAccDataSourceDNSKeys_basic(t *testing.T) { func TestAccDataSourceDNSKeys_noDnsSec(t *testing.T) { t.Parallel() - dnsZoneName := fmt.Sprintf("data-dnskey-test-%s", randString(t, 10)) + dnsZoneName := fmt.Sprintf("tf-dnskey-test-%s", randString(t, 10)) vcrTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckDNSManagedZoneDestroyProducer(t), + CheckDestroy: testAccCheckDNSManagedZoneDestroyProducerFramework(t), Steps: []resource.TestStep{ { - Config: testAccDataSourceDNSKeysConfig(dnsZoneName, "off"), + ExternalProviders: providerVersion450(), + Config: testAccDataSourceDNSKeysConfig(dnsZoneName, "off"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.google_dns_keys.foo_dns_key", "key_signing_keys.#", "0"), + resource.TestCheckResourceAttr("data.google_dns_keys.foo_dns_key", "zone_signing_keys.#", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(t), + Config: testAccDataSourceDNSKeysConfig(dnsZoneName, "off"), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("data.google_dns_keys.foo_dns_key", "key_signing_keys.#", "0"), resource.TestCheckResourceAttr("data.google_dns_keys.foo_dns_key", "zone_signing_keys.#", "0"), diff --git a/google-beta/data_source_dns_keys.go b/google-beta/data_source_dns_keys.go index eba0ced35d..f8a3d7212b 100644 --- a/google-beta/data_source_dns_keys.go +++ b/google-beta/data_source_dns_keys.go @@ -1,229 +1,435 @@ package google import ( + "context" "fmt" - "log" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "google.golang.org/api/dns/v1" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" ) -// DNSSEC Algorithm Numbers: https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml -// The following are algorithms that are supported by Cloud DNS -var dnssecAlgoNums = map[string]int{ - "rsasha1": 5, - "rsasha256": 8, - "rsasha512": 10, - "ecdsap256sha256": 13, - "ecdsap384sha384": 14, +var _ datasource.DataSource = &GoogleDnsKeysDataSource{} + +func NewGoogleDnsKeysDataSource() datasource.DataSource { + return &GoogleDnsKeysDataSource{} } -// DS RR Digest Types: https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml -// The following are digests that are supported by Cloud DNS -var dnssecDigestType = map[string]int{ - "sha1": 1, - "sha256": 2, - "sha384": 4, +// GoogleDnsKeysDataSource defines the data source implementation +type GoogleDnsKeysDataSource struct { + client *dns.Service + project types.String +} + +type GoogleDnsKeysModel struct { + Id types.String `tfsdk:"id"` + ManagedZone types.String `tfsdk:"managed_zone"` + Project types.String `tfsdk:"project"` + KeySigningKeys types.List `tfsdk:"key_signing_keys"` + ZoneSigningKeys types.List `tfsdk:"zone_signing_keys"` } -func dataSourceDNSKeys() *schema.Resource { - return &schema.Resource{ - Read: dataSourceDNSKeysRead, +type GoogleZoneSigningKey struct { + Algorithm types.String `tfsdk:"algorithm"` + CreationTime types.String `tfsdk:"creation_time"` + Description types.String `tfsdk:"description"` + Id types.String `tfsdk:"id"` + IsActive types.Bool `tfsdk:"is_active"` + KeyLength types.Int64 `tfsdk:"key_length"` + KeyTag types.Int64 `tfsdk:"key_tag"` + PublicKey types.String `tfsdk:"public_key"` + Digests types.List `tfsdk:"digests"` +} + +type GoogleKeySigningKey struct { + Algorithm types.String `tfsdk:"algorithm"` + CreationTime types.String `tfsdk:"creation_time"` + Description types.String `tfsdk:"description"` + Id types.String `tfsdk:"id"` + IsActive types.Bool `tfsdk:"is_active"` + KeyLength types.Int64 `tfsdk:"key_length"` + KeyTag types.Int64 `tfsdk:"key_tag"` + PublicKey types.String `tfsdk:"public_key"` + Digests types.List `tfsdk:"digests"` + + DSRecord types.String `tfsdk:"ds_record"` +} + +type GoogleZoneSigningKeyDigest struct { + Digest types.String `tfsdk:"digest"` + Type types.String `tfsdk:"type"` +} - Schema: map[string]*schema.Schema{ - "managed_zone": { - Type: schema.TypeString, - Required: true, - DiffSuppressFunc: compareSelfLinkOrResourceName, +var ( + digestAttrTypes = map[string]attr.Type{ + "digest": types.StringType, + "type": types.StringType, + } +) + +func (d *GoogleDnsKeysDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_dns_keys" +} + +func (d *GoogleDnsKeysDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "Get the DNSKEY and DS records of DNSSEC-signed managed zones", + + Attributes: map[string]schema.Attribute{ + "managed_zone": schema.StringAttribute{ + Description: "The Name of the zone.", + MarkdownDescription: "The Name of the zone.", + Required: true, }, - "project": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, + "project": schema.StringAttribute{ + Description: "The ID of the project for the Google Cloud.", + MarkdownDescription: "The ID of the project for the Google Cloud.", + Optional: true, + Computed: true, }, - "key_signing_keys": { - Type: schema.TypeList, - Computed: true, - Elem: kskResource(), + "id": schema.StringAttribute{ + Description: "DNS keys identifier", + MarkdownDescription: "DNS keys identifier", + Computed: true, }, - "zone_signing_keys": { - Type: schema.TypeList, - Computed: true, - Elem: dnsKeyResource(), + }, + Blocks: map[string]schema.Block{ + "zone_signing_keys": schema.ListNestedBlock{ + Description: "A list of Zone-signing key (ZSK) records.", + MarkdownDescription: "A list of Zone-signing key (ZSK) records.", + NestedObject: dnsKeyObject(), + }, + "key_signing_keys": schema.ListNestedBlock{ + Description: "A list of Key-signing key (KSK) records.", + MarkdownDescription: "A list of Key-signing key (KSK) records.", + NestedObject: kskObject(), }, }, } } -func dnsKeyResource() *schema.Resource { - return &schema.Resource{ - Schema: map[string]*schema.Schema{ - "algorithm": { - Type: schema.TypeString, +func (d *GoogleDnsKeysDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + p, ok := req.ProviderData.(*frameworkProvider) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *frameworkProvider, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + d.client = p.NewDnsClient(p.userAgent, &resp.Diagnostics) + d.project = p.project +} + +func (d *GoogleDnsKeysDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data GoogleDnsKeysModel + var metaData *ProviderMetaModel + var diags diag.Diagnostics + + // Read Provider meta into the meta model + resp.Diagnostics.Append(req.ProviderMeta.Get(ctx, &metaData)...) + if resp.Diagnostics.HasError() { + return + } + + d.client.UserAgent = generateFrameworkUserAgentString(metaData, d.client.UserAgent) + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + fv := parseProjectFieldValueFramework("managedZones", data.ManagedZone.ValueString(), "project", data.Project, d.project, false, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + data.Project = types.StringValue(fv.Project) + data.ManagedZone = types.StringValue(fv.Name) + + data.Id = types.StringValue(fmt.Sprintf("projects/%s/managedZones/%s", data.Project.ValueString(), data.ManagedZone.ValueString())) + + tflog.Debug(ctx, fmt.Sprintf("fetching DNS keys from managed zone %s", data.ManagedZone.ValueString())) + + clientResp, err := d.client.DnsKeys.List(data.Project.ValueString(), data.ManagedZone.ValueString()).Do() + if err != nil && !isGoogleApiErrorWithCode(err, 404) { + diags.AddError("error retrieving DNS keys", err.Error()) + return + } else if isGoogleApiErrorWithCode(err, 404) { + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + return + } + + tflog.Trace(ctx, "read dns keys data source") + + zoneSigningKeys, keySigningKeys := flattenSigningKeys(ctx, clientResp.DnsKeys, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + zskObjType := types.ObjectType{}.WithAttributeTypes(getDnsKeyAttrs("zoneSigning")) + data.ZoneSigningKeys, diags = types.ListValueFrom(ctx, zskObjType, zoneSigningKeys) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + kskObjType := types.ObjectType{}.WithAttributeTypes(getDnsKeyAttrs("keySigning")) + data.KeySigningKeys, diags = types.ListValueFrom(ctx, kskObjType, keySigningKeys) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +// dnsKeyObject is a helper function for the zone_signing_keys schema and +// is also used by key_signing_keys schema (called in kskObject defined below) +func dnsKeyObject() schema.NestedBlockObject { + return schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "algorithm": schema.StringAttribute{ + Description: "String mnemonic specifying the DNSSEC algorithm of this key. Immutable after creation time. " + + "Possible values are `ecdsap256sha256`, `ecdsap384sha384`, `rsasha1`, `rsasha256`, and `rsasha512`.", + MarkdownDescription: "String mnemonic specifying the DNSSEC algorithm of this key. Immutable after creation time. " + + "Possible values are `ecdsap256sha256`, `ecdsap384sha384`, `rsasha1`, `rsasha256`, and `rsasha512`.", Computed: true, }, - "creation_time": { - Type: schema.TypeString, - Computed: true, + "creation_time": schema.StringAttribute{ + Description: "The time that this resource was created in the control plane. This is in RFC3339 text format.", + MarkdownDescription: "The time that this resource was created in the control plane. This is in RFC3339 text format.", + Computed: true, }, - "description": { - Type: schema.TypeString, - Computed: true, + "description": schema.StringAttribute{ + Description: "A mutable string of at most 1024 characters associated with this resource for the user's convenience.", + MarkdownDescription: "A mutable string of at most 1024 characters associated with this resource for the user's convenience.", + Computed: true, }, - "digests": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "digest": { - Type: schema.TypeString, - Optional: true, - }, - "type": { - Type: schema.TypeString, - Optional: true, - }, - }, - }, + "id": schema.StringAttribute{ + Description: "Unique identifier for the resource; defined by the server.", + MarkdownDescription: "Unique identifier for the resource; defined by the server.", + Computed: true, }, - "id": { - Type: schema.TypeString, + "is_active": schema.BoolAttribute{ + Description: "Active keys will be used to sign subsequent changes to the ManagedZone. " + + "Inactive keys will still be present as DNSKEY Resource Records for the use of resolvers validating existing signatures.", + MarkdownDescription: "Active keys will be used to sign subsequent changes to the ManagedZone. " + + "Inactive keys will still be present as DNSKEY Resource Records for the use of resolvers validating existing signatures.", Computed: true, }, - "is_active": { - Type: schema.TypeBool, - Computed: true, + "key_length": schema.Int64Attribute{ + Description: "Length of the key in bits. Specified at creation time then immutable.", + MarkdownDescription: "Length of the key in bits. Specified at creation time then immutable.", + Computed: true, }, - "key_length": { - Type: schema.TypeInt, + "key_tag": schema.Int64Attribute{ + Description: "The key tag is a non-cryptographic hash of the a DNSKEY resource record associated with this DnsKey. " + + "The key tag can be used to identify a DNSKEY more quickly (but it is not a unique identifier). " + + "In particular, the key tag is used in a parent zone's DS record to point at the DNSKEY in this child ManagedZone. " + + "The key tag is a number in the range [0, 65535] and the algorithm to calculate it is specified in RFC4034 Appendix B.", + MarkdownDescription: "The key tag is a non-cryptographic hash of the a DNSKEY resource record associated with this DnsKey. " + + "The key tag can be used to identify a DNSKEY more quickly (but it is not a unique identifier). " + + "In particular, the key tag is used in a parent zone's DS record to point at the DNSKEY in this child ManagedZone. " + + "The key tag is a number in the range [0, 65535] and the algorithm to calculate it is specified in RFC4034 Appendix B.", Computed: true, }, - "key_tag": { - Type: schema.TypeInt, - Computed: true, + "public_key": schema.StringAttribute{ + Description: "Base64 encoded public half of this key.", + MarkdownDescription: "Base64 encoded public half of this key.", + Computed: true, }, - "public_key": { - Type: schema.TypeString, - Computed: true, + }, + Blocks: map[string]schema.Block{ + "digests": schema.ListNestedBlock{ + Description: "A list of cryptographic hashes of the DNSKEY resource record associated with this DnsKey. These digests are needed to construct a DS record that points at this DNS key.", + MarkdownDescription: "A list of cryptographic hashes of the DNSKEY resource record associated with this DnsKey. These digests are needed to construct a DS record that points at this DNS key.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "digest": schema.StringAttribute{ + Description: "The base-16 encoded bytes of this digest. Suitable for use in a DS resource record.", + MarkdownDescription: "The base-16 encoded bytes of this digest. Suitable for use in a DS resource record.", + Computed: true, + }, + "type": schema.StringAttribute{ + Description: "Specifies the algorithm used to calculate this digest. Possible values are `sha1`, `sha256` and `sha384`", + MarkdownDescription: "Specifies the algorithm used to calculate this digest. Possible values are `sha1`, `sha256` and `sha384`", + Computed: true, + }, + }, + }, }, }, } } -func kskResource() *schema.Resource { - resource := dnsKeyResource() +// kskObject is a helper function for the key_signing_keys schema +func kskObject() schema.NestedBlockObject { + nbo := dnsKeyObject() - resource.Schema["ds_record"] = &schema.Schema{ - Type: schema.TypeString, - Computed: true, + nbo.Attributes["ds_record"] = schema.StringAttribute{ + Description: "The DS record based on the KSK record.", + MarkdownDescription: "The DS record based on the KSK record.", + Computed: true, } - return resource + return nbo } -func generateDSRecord(signingKey *dns.DnsKey) (string, error) { - algoNum, found := dnssecAlgoNums[signingKey.Algorithm] - if !found { - return "", fmt.Errorf("DNSSEC Algorithm number for %s not found", signingKey.Algorithm) - } - - digestType, found := dnssecDigestType[signingKey.Digests[0].Type] - if !found { - return "", fmt.Errorf("DNSSEC Digest type for %s not found", signingKey.Digests[0].Type) - } - - return fmt.Sprintf("%d %d %d %s", - signingKey.KeyTag, - algoNum, - digestType, - signingKey.Digests[0].Digest), nil -} - -func flattenSigningKeys(signingKeys []*dns.DnsKey, keyType string) []map[string]interface{} { - var keys []map[string]interface{} +func flattenSigningKeys(ctx context.Context, signingKeys []*dns.DnsKey, diags *diag.Diagnostics) ([]types.Object, []types.Object) { + var zoneSigningKeys []types.Object + var keySigningKeys []types.Object + var d diag.Diagnostics for _, signingKey := range signingKeys { - if signingKey != nil && signingKey.Type == keyType { - data := map[string]interface{}{ - "algorithm": signingKey.Algorithm, - "creation_time": signingKey.CreationTime, - "description": signingKey.Description, - "digests": flattenDigests(signingKey.Digests), - "id": signingKey.Id, - "is_active": signingKey.IsActive, - "key_length": signingKey.KeyLength, - "key_tag": signingKey.KeyTag, - "public_key": signingKey.PublicKey, + if signingKey != nil { + var digests []types.Object + for _, dig := range signingKey.Digests { + digest := GoogleZoneSigningKeyDigest{ + Digest: types.StringValue(dig.Digest), + Type: types.StringValue(dig.Type), + } + obj, d := types.ObjectValueFrom(ctx, digestAttrTypes, digest) + diags.Append(d...) + if diags.HasError() { + return zoneSigningKeys, keySigningKeys + } + + digests = append(digests, obj) } if signingKey.Type == "keySigning" && len(signingKey.Digests) > 0 { + ksk := GoogleKeySigningKey{ + Algorithm: types.StringValue(signingKey.Algorithm), + CreationTime: types.StringValue(signingKey.CreationTime), + Description: types.StringValue(signingKey.Description), + Id: types.StringValue(signingKey.Id), + IsActive: types.BoolValue(signingKey.IsActive), + KeyLength: types.Int64Value(signingKey.KeyLength), + KeyTag: types.Int64Value(signingKey.KeyTag), + PublicKey: types.StringValue(signingKey.PublicKey), + } + + objType := types.ObjectType{}.WithAttributeTypes(digestAttrTypes) + ksk.Digests, d = types.ListValueFrom(ctx, objType, digests) + diags.Append(d...) + if diags.HasError() { + return zoneSigningKeys, keySigningKeys + } + dsRecord, err := generateDSRecord(signingKey) - if err == nil { - data["ds_record"] = dsRecord + if err != nil { + diags.AddError("error generating ds record", err.Error()) + return zoneSigningKeys, keySigningKeys } - } - keys = append(keys, data) - } - } + ksk.DSRecord = types.StringValue(dsRecord) - return keys -} + obj, d := types.ObjectValueFrom(ctx, getDnsKeyAttrs(signingKey.Type), ksk) + diags.Append(d...) + if diags.HasError() { + return zoneSigningKeys, keySigningKeys + } + keySigningKeys = append(keySigningKeys, obj) + } else { + zsk := GoogleZoneSigningKey{ + Algorithm: types.StringValue(signingKey.Algorithm), + CreationTime: types.StringValue(signingKey.CreationTime), + Description: types.StringValue(signingKey.Description), + Id: types.StringValue(signingKey.Id), + IsActive: types.BoolValue(signingKey.IsActive), + KeyLength: types.Int64Value(signingKey.KeyLength), + KeyTag: types.Int64Value(signingKey.KeyTag), + PublicKey: types.StringValue(signingKey.PublicKey), + } -func flattenDigests(dnsKeyDigests []*dns.DnsKeyDigest) []map[string]interface{} { - var digests []map[string]interface{} + objType := types.ObjectType{}.WithAttributeTypes(digestAttrTypes) + zsk.Digests, d = types.ListValueFrom(ctx, objType, digests) + diags.Append(d...) + if diags.HasError() { + return zoneSigningKeys, keySigningKeys + } - for _, dnsKeyDigest := range dnsKeyDigests { - if dnsKeyDigest != nil { - data := map[string]interface{}{ - "digest": dnsKeyDigest.Digest, - "type": dnsKeyDigest.Type, + obj, d := types.ObjectValueFrom(ctx, getDnsKeyAttrs("zoneSigning"), zsk) + diags.Append(d...) + if diags.HasError() { + return zoneSigningKeys, keySigningKeys + } + zoneSigningKeys = append(zoneSigningKeys, obj) } - digests = append(digests, data) } } - return digests + return zoneSigningKeys, keySigningKeys } -func dataSourceDNSKeysRead(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - userAgent, err := generateUserAgentString(d, config.userAgent) - if err != nil { - return err - } +// DNSSEC Algorithm Numbers: https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml +// The following are algorithms that are supported by Cloud DNS +var dnssecAlgoNums = map[string]int{ + "rsasha1": 5, + "rsasha256": 8, + "rsasha512": 10, + "ecdsap256sha256": 13, + "ecdsap384sha384": 14, +} - fv, err := parseProjectFieldValue("managedZones", d.Get("managed_zone").(string), "project", d, config, false) - if err != nil { - return err - } - project := fv.Project - managedZone := fv.Name +// DS RR Digest Types: https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml +// The following are digests that are supported by Cloud DNS +var dnssecDigestType = map[string]int{ + "sha1": 1, + "sha256": 2, + "sha384": 4, +} - if err := d.Set("project", project); err != nil { - return fmt.Errorf("Error setting project: %s", err) +// generateDSRecord will generate the ds_record on key signing keys +func generateDSRecord(signingKey *dns.DnsKey) (string, error) { + algoNum, found := dnssecAlgoNums[signingKey.Algorithm] + if !found { + return "", fmt.Errorf("DNSSEC Algorithm number for %s not found", signingKey.Algorithm) } - d.SetId(fmt.Sprintf("projects/%s/managedZones/%s", project, managedZone)) - log.Printf("[DEBUG] Fetching DNS keys from managed zone %s", managedZone) - - response, err := config.NewDnsClient(userAgent).DnsKeys.List(project, managedZone).Do() - if err != nil && !isGoogleApiErrorWithCode(err, 404) { - return fmt.Errorf("error retrieving DNS keys: %s", err) - } else if isGoogleApiErrorWithCode(err, 404) { - return nil + digestType, found := dnssecDigestType[signingKey.Digests[0].Type] + if !found { + return "", fmt.Errorf("DNSSEC Digest type for %s not found", signingKey.Digests[0].Type) } - log.Printf("[DEBUG] Fetched DNS keys from managed zone %s", managedZone) + return fmt.Sprintf("%d %d %d %s", + signingKey.KeyTag, + algoNum, + digestType, + signingKey.Digests[0].Digest), nil +} - if err := d.Set("key_signing_keys", flattenSigningKeys(response.DnsKeys, "keySigning")); err != nil { - return fmt.Errorf("Error setting key_signing_keys: %s", err) +func getDnsKeyAttrs(keyType string) map[string]attr.Type { + dnsKeyAttrs := map[string]attr.Type{ + "algorithm": types.StringType, + "creation_time": types.StringType, + "description": types.StringType, + "id": types.StringType, + "is_active": types.BoolType, + "key_length": types.Int64Type, + "key_tag": types.Int64Type, + "public_key": types.StringType, + "digests": types.ListType{}.WithElementType(types.ObjectType{}.WithAttributeTypes(digestAttrTypes)), } - if err := d.Set("zone_signing_keys", flattenSigningKeys(response.DnsKeys, "zoneSigning")); err != nil { - return fmt.Errorf("Error setting zone_signing_keys: %s", err) + + if keyType == "keySigning" { + dnsKeyAttrs["ds_record"] = types.StringType } - return nil + return dnsKeyAttrs } diff --git a/google-beta/framework_provider.go b/google-beta/framework_provider.go index 537466eb2e..54edbb6212 100644 --- a/google-beta/framework_provider.go +++ b/google-beta/framework_provider.go @@ -930,6 +930,7 @@ func (p *frameworkProvider) DataSources(_ context.Context) []func() datasource.D return []func() datasource.DataSource{ NewGoogleDnsManagedZoneDataSource, NewGoogleDnsRecordSetDataSource, + NewGoogleDnsKeysDataSource, } } diff --git a/google-beta/framework_utils.go b/google-beta/framework_utils.go index 5a377f583a..5670c1a71f 100644 --- a/google-beta/framework_utils.go +++ b/google-beta/framework_utils.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + "regexp" "strings" "github.com/hashicorp/terraform-plugin-framework/diag" @@ -87,3 +88,52 @@ func handleDatasourceNotFoundError(ctx context.Context, err error, state *tfsdk. diags.AddError(fmt.Sprintf("Error when reading or editing %s", resource), err.Error()) } + +// field helpers + +// Parses a project field with the following formats: +// - projects/{my_projects}/{resource_type}/{resource_name} +func parseProjectFieldValueFramework(resourceType, fieldValue, projectSchemaField string, rVal, pVal types.String, isEmptyValid bool, diags *diag.Diagnostics) *ProjectFieldValue { + if len(fieldValue) == 0 { + if isEmptyValid { + return &ProjectFieldValue{resourceType: resourceType} + } + diags.AddError("field can not be empty", fmt.Sprintf("The project field for resource %s cannot be empty", resourceType)) + return nil + } + + r := regexp.MustCompile(fmt.Sprintf(projectBasePattern, resourceType)) + if parts := r.FindStringSubmatch(fieldValue); parts != nil { + return &ProjectFieldValue{ + Project: parts[1], + Name: parts[2], + + resourceType: resourceType, + } + } + + project := getProjectFromFrameworkSchema(projectSchemaField, rVal, pVal, diags) + if diags.HasError() { + return nil + } + + return &ProjectFieldValue{ + Project: project.ValueString(), + Name: GetResourceNameFromSelfLink(fieldValue), + + resourceType: resourceType, + } +} + +func getProjectFromFrameworkSchema(projectSchemaField string, rVal, pVal types.String, diags *diag.Diagnostics) types.String { + if !rVal.IsNull() && projectSchemaField != "" { + return rVal + } + + if !pVal.IsNull() { + return pVal + } + + diags.AddError("required field is not set", fmt.Sprintf("%s must be set", projectSchemaField)) + return types.String{} +} diff --git a/google-beta/provider.go b/google-beta/provider.go index 03cfe76db5..8d8390ca58 100644 --- a/google-beta/provider.go +++ b/google-beta/provider.go @@ -699,7 +699,6 @@ func Provider() *schema.Provider { "google_container_registry_image": dataSourceGoogleContainerImage(), "google_container_registry_repository": dataSourceGoogleContainerRepo(), "google_dataproc_metastore_service": dataSourceDataprocMetastoreService(), - "google_dns_keys": dataSourceDNSKeys(), "google_game_services_game_server_deployment_rollout": dataSourceGameServicesGameServerDeploymentRollout(), "google_iam_policy": dataSourceGoogleIamPolicy(), "google_iam_role": dataSourceGoogleIamRole(),