Skip to content

Commit 9228fe7

Browse files
Private CA - Create expander for X509Config (#5368) (#10702)
* Create custom expander for X509Config In order to handle the case of optional primitive fields, I've added virtual fields to allow the expander to distinguish between an unset primitive and a primitive set to the default value. Since some Private CA resources do not support updates, I also added support for setting ForceNew in the terraform config. * Add error handling for expansion This prevents incompatiable configs that set the allow* booleans without setting basic constraints, and vice versa. * Change virtual field names, and other feedback * Update examples with virtual fields * Use url_param_only instead of virtual field. * Handle CertificateAuthority resource which does not have field include_is_ca * Fix format issue * * Update description for fields `include_is_ca` `include_max_issuer_path_path` to reflect its current functionality * Add field `include_is_ca` to CertificateAuthority to avoding checking the existence of this field in flattener. * Update examples with new fields like `include_is_ca`, `include_max_issuer_path_length`. * Update description; Add test cases for CaOption * fix a typo * remove include_x from template resource * Update semantic meaning for newly added fields to avoid breaking changes. * User `nonCa`, `zeroMaxIssuerPathLength` instead of `includeIsCa` `includeMaxIssuerPathLength` * Update test cases. * Create custom expander for X509Config In order to handle the case of optional primitive fields, I've added virtual fields to allow the expander to distinguish between an unset primitive and a primitive set to the default value. Since some Private CA resources do not support updates, I also added support for setting ForceNew in the terraform config. * Add error handling for expansion This prevents incompatiable configs that set the allow* booleans without setting basic constraints, and vice versa. * Change virtual field names, and other feedback * Update examples with virtual fields * Use url_param_only instead of virtual field. * Handle CertificateAuthority resource which does not have field include_is_ca * Fix format issue * * Update description for fields `include_is_ca` `include_max_issuer_path_path` to reflect its current functionality * Add field `include_is_ca` to CertificateAuthority to avoding checking the existence of this field in flattener. * Update examples with new fields like `include_is_ca`, `include_max_issuer_path_length`. * Update description; Add test cases for CaOption * fix a typo * remove include_x from template resource * Update semantic meaning for newly added fields to avoid breaking changes. * User `nonCa`, `zeroMaxIssuerPathLength` instead of `includeIsCa` `includeMaxIssuerPathLength` * Update test cases. * Update doc-string for fields in CaOptions Co-authored-by: Yong Cao <[email protected]> Signed-off-by: Modular Magician <[email protected]> Co-authored-by: Yong Cao <[email protected]>
1 parent 30223c0 commit 9228fe7

11 files changed

+509
-1220
lines changed

.changelog/5368.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
privateca: added support for setting default values for basic constraints for `google_privateca_certificate`, `google_privateca_certificate_authority`, and `google_privateca_ca_pool` via the `non_ca` and `zero_max_issuer_path_length` fields
3+
```

google/privateca_utils.go

+232-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package google
22

33
import (
4+
"fmt"
45
"strconv"
56

67
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -13,6 +14,224 @@ import (
1314
// deleted on the API-side. This flattener creates default objects for sub-objects that match this pattern
1415
// to fix perma-diffs on default-only objects. For this to work all objects that are flattened from nil to
1516
// their default object *MUST* be set in the user's config, so they are all marked as Required in the schema.
17+
//
18+
// This file also contains shared expanders between the above resources. The expanders are required in order
19+
// to handle the optional primitive field in CaOptions. By adding a virtual field, the expander can distinguish
20+
// between an unset primitive field and a set primitive field with a default value.
21+
22+
// Expander utilities
23+
24+
func expandPrivatecaCertificateConfigX509ConfigCaOptions(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
25+
// Fields non_ca, zero_max_issuer_path_length are used to distinguish between
26+
// unset booleans and booleans set with a default value.
27+
// Unset is_ca or unset max_issuer_path_length either allow any values for these fields when
28+
// used in an issuance policy, or allow the API to use default values when used in a
29+
// certificate config. A default value of is_ca=false means that issued certificates cannot
30+
// be CA certificates. A default value of max_issuer_path_length=0 means that the CA cannot
31+
// issue CA certificates.
32+
if v == nil {
33+
return nil, nil
34+
}
35+
l := v.([]interface{})
36+
if len(l) == 0 || l[0] == nil {
37+
return nil, nil
38+
}
39+
raw := l[0]
40+
original := raw.(map[string]interface{})
41+
42+
nonCa := original["non_ca"].(bool)
43+
isCa := original["is_ca"].(bool)
44+
45+
zeroPathLength := original["zero_max_issuer_path_length"].(bool)
46+
maxIssuerPathLength := original["max_issuer_path_length"].(int)
47+
48+
transformed := make(map[string]interface{})
49+
50+
if nonCa && isCa {
51+
return nil, fmt.Errorf("non_ca, is_ca can not be set to true at the same time.")
52+
}
53+
if zeroPathLength && maxIssuerPathLength > 0 {
54+
return nil, fmt.Errorf("zero_max_issuer_path_length can not be set to true while max_issuer_path_length being set to a positive integer.")
55+
}
56+
57+
if isCa || nonCa {
58+
transformed["isCa"] = original["is_ca"]
59+
}
60+
if maxIssuerPathLength > 0 || zeroPathLength {
61+
transformed["maxIssuerPathLength"] = original["max_issuer_path_length"]
62+
}
63+
return transformed, nil
64+
}
65+
66+
func expandPrivatecaCertificateConfigX509ConfigKeyUsage(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
67+
if v == nil {
68+
return v, nil
69+
}
70+
l := v.([]interface{})
71+
if len(l) == 0 || l[0] == nil {
72+
return nil, nil
73+
}
74+
75+
raw := l[0]
76+
original := raw.(map[string]interface{})
77+
if len(original) == 0 {
78+
// Ignore empty KeyUsage
79+
return nil, nil
80+
}
81+
transformed := make(map[string]interface{})
82+
transformed["baseKeyUsage"] =
83+
expandPrivatecaCertificateConfigX509ConfigKeyUsageBaseKeyUsage(original["base_key_usage"], d, config)
84+
transformed["extendedKeyUsage"] =
85+
expandPrivatecaCertificateConfigX509ConfigKeyUsageExtendedKeyUsage(original["extended_key_usage"], d, config)
86+
transformed["unknownExtendedKeyUsages"] =
87+
expandPrivatecaCertificateConfigX509ConfigKeyUsageUnknownExtendedKeyUsages(original["unknown_extended_key_usages"], d, config)
88+
89+
return transformed, nil
90+
}
91+
92+
func expandPrivatecaCertificateConfigX509ConfigKeyUsageBaseKeyUsage(v interface{}, d TerraformResourceData, config *Config) interface{} {
93+
if v == nil {
94+
return v
95+
}
96+
l := v.([]interface{})
97+
if len(l) == 0 || l[0] == nil {
98+
return nil
99+
}
100+
101+
raw := l[0]
102+
original := raw.(map[string]interface{})
103+
if len(original) == 0 {
104+
// Ignore empty BaseKeyUsage
105+
return nil
106+
}
107+
108+
transformed := make(map[string]interface{})
109+
transformed["digitalSignature"] = original["digital_signature"]
110+
transformed["contentCommitment"] = original["content_commitment"]
111+
transformed["keyEncipherment"] = original["key_encipherment"]
112+
transformed["dataEncipherment"] = original["data_encipherment"]
113+
transformed["keyAgreement"] = original["key_agreement"]
114+
transformed["certSign"] = original["cert_sign"]
115+
transformed["crlSign"] = original["crl_sign"]
116+
transformed["encipherOnly"] = original["encipher_only"]
117+
transformed["decipherOnly"] = original["decipher_only"]
118+
return transformed
119+
}
120+
121+
func expandPrivatecaCertificateConfigX509ConfigKeyUsageExtendedKeyUsage(v interface{}, d TerraformResourceData, config *Config) interface{} {
122+
if v == nil {
123+
return v
124+
}
125+
l := v.([]interface{})
126+
if len(l) == 0 || l[0] == nil {
127+
return nil
128+
}
129+
130+
raw := l[0]
131+
original := raw.(map[string]interface{})
132+
if len(original) == 0 {
133+
// Ignore empty ExtendedKeyUsage
134+
return nil
135+
}
136+
137+
transformed := make(map[string]interface{})
138+
transformed["serverAuth"] = original["server_auth"]
139+
transformed["clientAuth"] = original["client_auth"]
140+
transformed["codeSigning"] = original["code_signing"]
141+
transformed["emailProtection"] = original["email_protection"]
142+
transformed["timeStamping"] = original["time_stamping"]
143+
transformed["ocspSigning"] = original["ocsp_signing"]
144+
return transformed
145+
}
146+
147+
func expandPrivatecaCertificateConfigX509ConfigKeyUsageUnknownExtendedKeyUsages(v interface{}, d TerraformResourceData, config *Config) interface{} {
148+
if v == nil {
149+
return v
150+
}
151+
l := v.([]interface{})
152+
transformed := make([]interface{}, 0, len(l))
153+
// Parses the list of object IDs
154+
for _, raw := range l {
155+
original := raw.(map[string]interface{})
156+
if len(original) < 1 {
157+
// Ignore empty UnknownExtendedKeyUsages
158+
continue
159+
}
160+
transformed = append(transformed, map[string]interface{}{
161+
"objectIdPath": original["object_id_path"],
162+
})
163+
}
164+
return transformed
165+
}
166+
167+
func expandPrivatecaCertificateConfigX509ConfigPolicyIds(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
168+
if v == nil {
169+
return v, nil
170+
}
171+
l := v.([]interface{})
172+
transformed := make([]interface{}, 0, len(l))
173+
// Parses the list of object IDs
174+
for _, raw := range l {
175+
original := raw.(map[string]interface{})
176+
if len(original) < 1 {
177+
// Ignore empty ObjectId
178+
continue
179+
}
180+
transformed = append(transformed, map[string]interface{}{
181+
"objectIdPath": original["object_id_path"],
182+
})
183+
}
184+
return transformed, nil
185+
}
186+
187+
func expandPrivatecaCertificateConfigX509ConfigAdditionalExtensions(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
188+
if v == nil {
189+
return v, nil
190+
}
191+
l := v.([]interface{})
192+
transformed := make([]interface{}, 0, len(l))
193+
for _, raw := range l {
194+
original := raw.(map[string]interface{})
195+
if len(original) < 1 {
196+
// Ignore empty AdditionalExtensions
197+
continue
198+
}
199+
transformed = append(transformed, map[string]interface{}{
200+
"critical": original["critical"],
201+
"value": original["value"],
202+
"objectId": expandPrivatecaCertificateConfigX509ConfigAdditionalExtensionsObjectId(original["object_id"], d, config),
203+
})
204+
}
205+
return transformed, nil
206+
}
207+
208+
func expandPrivatecaCertificateConfigX509ConfigAdditionalExtensionsObjectId(v interface{}, d TerraformResourceData, config *Config) interface{} {
209+
if v == nil {
210+
return v
211+
}
212+
l := v.([]interface{})
213+
if len(l) == 0 || l[0] == nil {
214+
return nil
215+
}
216+
217+
// Expects a single object ID.
218+
raw := l[0]
219+
original := raw.(map[string]interface{})
220+
if len(original) == 0 {
221+
// Ignore empty ObjectId
222+
return nil
223+
}
224+
transformed := make(map[string]interface{})
225+
transformed["objectIdPath"] = original["object_id_path"]
226+
return transformed
227+
}
228+
229+
func expandPrivatecaCertificateConfigX509ConfigAiaOcspServers(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
230+
// List of strings, no processing necessary.
231+
return v, nil
232+
}
233+
234+
// Flattener utilities
16235

17236
func flattenPrivatecaCertificateConfigX509ConfigAdditionalExtensions(v interface{}, d *schema.ResourceData, config *Config) interface{} {
18237
if v == nil {
@@ -90,17 +309,24 @@ func flattenPrivatecaCertificateConfigX509ConfigCaOptions(v interface{}, d *sche
90309
// and CertificateAuthority APIs.
91310
if v == nil || len(v.(map[string]interface{})) == 0 {
92311
v = make(map[string]interface{})
93-
original := v.(map[string]interface{})
94-
transformed := make(map[string]interface{})
95-
transformed["is_ca"] = flattenPrivatecaCertificateConfigX509ConfigCaOptionsIsCa(original["isCa"], d, config)
96-
return []interface{}{transformed}
97312
}
98313
original := v.(map[string]interface{})
99314
transformed := make(map[string]interface{})
315+
316+
val, exists := original["isCa"]
100317
transformed["is_ca"] =
101-
flattenPrivatecaCertificateConfigX509ConfigCaOptionsIsCa(original["isCa"], d, config)
318+
flattenPrivatecaCertificateConfigX509ConfigCaOptionsIsCa(val, d, config)
319+
if exists && !val.(bool) {
320+
transformed["non_ca"] = true
321+
}
322+
323+
val, exists = original["maxIssuerPathLength"]
102324
transformed["max_issuer_path_length"] =
103-
flattenPrivatecaCertificateConfigX509ConfigCaOptionsMaxIssuerPathLength(original["maxIssuerPathLength"], d, config)
325+
flattenPrivatecaCertificateConfigX509ConfigCaOptionsMaxIssuerPathLength(val, d, config)
326+
if exists && int(val.(float64)) == 0 {
327+
transformed["zero_max_issuer_path_length"] = true
328+
}
329+
104330
return []interface{}{transformed}
105331
}
106332
func flattenPrivatecaCertificateConfigX509ConfigCaOptionsIsCa(v interface{}, d *schema.ResourceData, config *Config) interface{} {

0 commit comments

Comments
 (0)