From 9cb7e88b30aa590d8d030da26d8c3aff9a5c88a2 Mon Sep 17 00:00:00 2001 From: Modular Magician Date: Fri, 15 May 2020 18:21:57 +0000 Subject: [PATCH] Composer access control (#3503) * Create works, update needs help * non-plural name for set items * Add sql and web server ip cidr blocks * Test passes * Remove schema from ga version, fix lint erro * Add note to composer docs linking to private environment setup * Computed web server access as it has default values set by API. Ignore during import tests * Add computed to new cidr blocks as they get default values based on the region they exist in Signed-off-by: Modular Magician --- .changelog/3503.txt | 9 ++ google-beta/resource_composer_environment.go | 137 ++++++++++++++++ .../resource_composer_environment_test.go | 152 ++++++++++++++++++ .../docs/r/composer_environment.html.markdown | 32 +++- 4 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 .changelog/3503.txt diff --git a/.changelog/3503.txt b/.changelog/3503.txt new file mode 100644 index 0000000000..e40c36870f --- /dev/null +++ b/.changelog/3503.txt @@ -0,0 +1,9 @@ +```release-note:enhancement +composer: Added support for `google_composer_environment` `web_server_network_access_control` for private environments (Beta only) +``` +```release-note:enhancement +composer: Added support for `google_composer_environment` `config.private_environment_config.cloud_sql_ipv4_cidr_block` +``` +```release-note:enhancement +composer: Added support for `google_composer_environment` `config.private_environment_config.web_server_ipv4_cidr_block` +``` diff --git a/google-beta/resource_composer_environment.go b/google-beta/resource_composer_environment.go index 123e8823f9..4eaae23d42 100644 --- a/google-beta/resource_composer_environment.go +++ b/google-beta/resource_composer_environment.go @@ -49,6 +49,20 @@ var ( "config.0.node_config", "config.0.software_config", "config.0.private_environment_config", + "config.0.web_server_network_access_control", + } + + allowedIpRangesConfig = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Type: schema.TypeString, + Required: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + }, } ) @@ -277,6 +291,8 @@ func resourceComposerEnvironment() *schema.Resource { AtLeastOneOf: []string{ "config.0.private_environment_config.0.enable_private_endpoint", "config.0.private_environment_config.0.master_ipv4_cidr_block", + "config.0.private_environment_config.0.cloud_sql_ipv4_cidr_block", + "config.0.private_environment_config.0.web_server_ipv4_cidr_block", }, ForceNew: true, }, @@ -286,10 +302,52 @@ func resourceComposerEnvironment() *schema.Resource { AtLeastOneOf: []string{ "config.0.private_environment_config.0.enable_private_endpoint", "config.0.private_environment_config.0.master_ipv4_cidr_block", + "config.0.private_environment_config.0.cloud_sql_ipv4_cidr_block", + "config.0.private_environment_config.0.web_server_ipv4_cidr_block", }, ForceNew: true, Default: "172.16.0.0/28", }, + "web_server_ipv4_cidr_block": { + Type: schema.TypeString, + Optional: true, + Computed: true, + AtLeastOneOf: []string{ + "config.0.private_environment_config.0.enable_private_endpoint", + "config.0.private_environment_config.0.master_ipv4_cidr_block", + "config.0.private_environment_config.0.cloud_sql_ipv4_cidr_block", + "config.0.private_environment_config.0.web_server_ipv4_cidr_block", + }, + ForceNew: true, + }, + "cloud_sql_ipv4_cidr_block": { + Type: schema.TypeString, + Optional: true, + Computed: true, + AtLeastOneOf: []string{ + "config.0.private_environment_config.0.enable_private_endpoint", + "config.0.private_environment_config.0.master_ipv4_cidr_block", + "config.0.private_environment_config.0.cloud_sql_ipv4_cidr_block", + "config.0.private_environment_config.0.web_server_ipv4_cidr_block", + }, + ForceNew: true, + }, + }, + }, + }, + "web_server_network_access_control": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "allowed_ip_range": { + Type: schema.TypeSet, + Computed: true, + Optional: true, + Elem: allowedIpRangesConfig, + }, }, }, }, @@ -512,6 +570,18 @@ func resourceComposerEnvironmentUpdate(d *schema.ResourceData, meta interface{}) } d.SetPartial("config") } + + if d.HasChange("config.0.web_server_network_access_control") { + patchObj := &composer.Environment{Config: &composer.EnvironmentConfig{}} + if config != nil { + patchObj.Config.WebServerNetworkAccessControl = config.WebServerNetworkAccessControl + } + err = resourceComposerEnvironmentPatchField("config.webServerNetworkAccessControl", patchObj, d, tfConfig) + if err != nil { + return err + } + d.SetPartial("config") + } } if d.HasChange("labels") { @@ -628,10 +698,32 @@ func flattenComposerEnvironmentConfig(envCfg *composer.EnvironmentConfig) interf transformed["node_config"] = flattenComposerEnvironmentConfigNodeConfig(envCfg.NodeConfig) transformed["software_config"] = flattenComposerEnvironmentConfigSoftwareConfig(envCfg.SoftwareConfig) transformed["private_environment_config"] = flattenComposerEnvironmentConfigPrivateEnvironmentConfig(envCfg.PrivateEnvironmentConfig) + transformed["web_server_network_access_control"] = flattenComposerEnvironmentConfigWebServerNetworkAccessControl(envCfg.WebServerNetworkAccessControl) return []interface{}{transformed} } +func flattenComposerEnvironmentConfigWebServerNetworkAccessControl(accessControl *composer.WebServerNetworkAccessControl) interface{} { + if accessControl == nil || accessControl.AllowedIpRanges == nil { + return nil + } + + transformed := make([]interface{}, 0, len(accessControl.AllowedIpRanges)) + for _, ipRange := range accessControl.AllowedIpRanges { + data := map[string]interface{}{ + "value": ipRange.Value, + "description": ipRange.Description, + } + transformed = append(transformed, data) + } + + webServerNetworkAccessControl := make(map[string]interface{}) + + webServerNetworkAccessControl["allowed_ip_range"] = schema.NewSet(schema.HashResource(allowedIpRangesConfig), transformed) + + return []interface{}{webServerNetworkAccessControl} +} + func flattenComposerEnvironmentConfigPrivateEnvironmentConfig(envCfg *composer.PrivateEnvironmentConfig) interface{} { if envCfg == nil { return nil @@ -640,6 +732,8 @@ func flattenComposerEnvironmentConfigPrivateEnvironmentConfig(envCfg *composer.P transformed := make(map[string]interface{}) transformed["enable_private_endpoint"] = envCfg.PrivateClusterConfig.EnablePrivateEndpoint transformed["master_ipv4_cidr_block"] = envCfg.PrivateClusterConfig.MasterIpv4CidrBlock + transformed["cloud_sql_ipv4_cidr_block"] = envCfg.CloudSqlIpv4CidrBlock + transformed["web_server_ipv4_cidr_block"] = envCfg.WebServerIpv4CidrBlock return []interface{}{transformed} } @@ -736,6 +830,12 @@ func expandComposerEnvironmentConfig(v interface{}, d *schema.ResourceData, conf } transformed.PrivateEnvironmentConfig = transformedPrivateEnvironmentConfig + transformedWebServerNetworkAccessControl, err := expandComposerEnvironmentConfigWebServerNetworkAccessControl(original["web_server_network_access_control"], d, config) + if err != nil { + return nil, err + } + transformed.WebServerNetworkAccessControl = transformedWebServerNetworkAccessControl + return transformed, nil } @@ -746,6 +846,35 @@ func expandComposerEnvironmentConfigNodeCount(v interface{}, d *schema.ResourceD return int64(v.(int)), nil } +func expandComposerEnvironmentConfigWebServerNetworkAccessControl(v interface{}, d *schema.ResourceData, config *Config) (*composer.WebServerNetworkAccessControl, error) { + l := v.([]interface{}) + if len(l) == 0 { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + + allowedIpRangesRaw := original["allowed_ip_range"].(*schema.Set).List() + if len(allowedIpRangesRaw) == 0 { + return nil, nil + } + + transformed := &composer.WebServerNetworkAccessControl{} + allowedIpRanges := make([]*composer.AllowedIpRange, 0, len(original)) + + for _, originalIpRange := range allowedIpRangesRaw { + originalRangeRaw := originalIpRange.(map[string]interface{}) + transformedRange := &composer.AllowedIpRange{Value: originalRangeRaw["value"].(string)} + if v, ok := originalRangeRaw["description"]; ok { + transformedRange.Description = v.(string) + } + allowedIpRanges = append(allowedIpRanges, transformedRange) + } + + transformed.AllowedIpRanges = allowedIpRanges + return transformed, nil +} + func expandComposerEnvironmentConfigPrivateEnvironmentConfig(v interface{}, d *schema.ResourceData, config *Config) (*composer.PrivateEnvironmentConfig, error) { l := v.([]interface{}) if len(l) == 0 { @@ -767,6 +896,14 @@ func expandComposerEnvironmentConfigPrivateEnvironmentConfig(v interface{}, d *s subBlock.MasterIpv4CidrBlock = v.(string) } + if v, ok := original["cloud_sql_ipv4_cidr_block"]; ok { + transformed.CloudSqlIpv4CidrBlock = v.(string) + } + + if v, ok := original["web_server_ipv4_cidr_block"]; ok { + transformed.WebServerIpv4CidrBlock = v.(string) + } + transformed.PrivateClusterConfig = subBlock return transformed, nil diff --git a/google-beta/resource_composer_environment_test.go b/google-beta/resource_composer_environment_test.go index 52df82d434..6c4d396089 100644 --- a/google-beta/resource_composer_environment_test.go +++ b/google-beta/resource_composer_environment_test.go @@ -172,6 +172,54 @@ func TestAccComposerEnvironment_private(t *testing.T) { }) } +// Checks environment creation with minimum required information. +func TestAccComposerEnvironment_privateWithWebServerControl(t *testing.T) { + t.Parallel() + + envName := fmt.Sprintf("%s-%d", testComposerEnvironmentPrefix, randInt(t)) + network := fmt.Sprintf("%s-%d", testComposerNetworkPrefix, randInt(t)) + subnetwork := network + "-1" + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccComposerEnvironmentDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComposerEnvironment_privateWithWebServerControl(envName, network, subnetwork), + }, + { + ResourceName: "google_composer_environment.test", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComposerEnvironment_privateWithWebServerControlUpdated(envName, network, subnetwork), + }, + { + ResourceName: "google_composer_environment.test", + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "google_composer_environment.test", + ImportState: true, + ImportStateId: fmt.Sprintf("projects/%s/locations/%s/environments/%s", getTestProjectFromEnv(), "us-central1", envName), + ImportStateVerify: true, + }, + // This is a terrible clean-up step in order to get destroy to succeed, + // due to dangling firewall rules left by the Composer Environment blocking network deletion. + // TODO(emilyye): Remove this check if firewall rules bug gets fixed by Composer. + { + PlanOnly: true, + ExpectNonEmptyPlan: false, + Config: testAccComposerEnvironment_privateWithWebServerControlUpdated(envName, network, subnetwork), + Check: testAccCheckClearComposerEnvironmentFirewalls(t, network), + }, + }, + }) +} + // Checks behavior of node config, including dependencies on Compute resources. func TestAccComposerEnvironment_withNodeConfig(t *testing.T) { t.Parallel() @@ -372,6 +420,110 @@ resource "google_compute_subnetwork" "test" { `, name, network, subnetwork) } +func testAccComposerEnvironment_privateWithWebServerControl(name, network, subnetwork string) string { + return fmt.Sprintf(` +resource "google_composer_environment" "test" { + name = "%s" + region = "us-central1" + + config { + node_config { + network = google_compute_network.test.self_link + subnetwork = google_compute_subnetwork.test.self_link + zone = "us-central1-a" + ip_allocation_policy { + use_ip_aliases = true + cluster_ipv4_cidr_block = "10.56.0.0/14" + services_ipv4_cidr_block = "10.122.0.0/20" + } + } + private_environment_config { + enable_private_endpoint = false + web_server_ipv4_cidr_block = "172.30.240.0/24" + cloud_sql_ipv4_cidr_block = "10.32.0.0/12" + master_ipv4_cidr_block = "172.17.50.0/28" + } + web_server_network_access_control { + allowed_ip_range { + value = "192.168.0.1" + description = "my range1" + } + allowed_ip_range { + value = "0.0.0.0/0" + } + } + } +} + +// use a separate network to avoid conflicts with other tests running in parallel +// that use the default network/subnet +resource "google_compute_network" "test" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "test" { + name = "%s" + ip_cidr_range = "10.2.0.0/16" + region = "us-central1" + network = google_compute_network.test.self_link + private_ip_google_access = true +} +`, name, network, subnetwork) +} + +func testAccComposerEnvironment_privateWithWebServerControlUpdated(name, network, subnetwork string) string { + return fmt.Sprintf(` +resource "google_composer_environment" "test" { + name = "%s" + region = "us-central1" + + config { + node_config { + network = google_compute_network.test.self_link + subnetwork = google_compute_subnetwork.test.self_link + zone = "us-central1-a" + ip_allocation_policy { + use_ip_aliases = true + cluster_ipv4_cidr_block = "10.56.0.0/14" + services_ipv4_cidr_block = "10.122.0.0/20" + } + } + private_environment_config { + enable_private_endpoint = false + web_server_ipv4_cidr_block = "172.30.240.0/24" + cloud_sql_ipv4_cidr_block = "10.32.0.0/12" + master_ipv4_cidr_block = "172.17.50.0/28" + } + web_server_network_access_control { + allowed_ip_range { + value = "192.168.0.1" + description = "my range1" + } + allowed_ip_range { + value = "0.0.0.0/0" + } + } + } +} + +// use a separate network to avoid conflicts with other tests running in parallel +// that use the default network/subnet +resource "google_compute_network" "test" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "test" { + name = "%s" + ip_cidr_range = "10.2.0.0/16" + region = "us-central1" + network = google_compute_network.test.self_link + private_ip_google_access = true +} +`, name, network, subnetwork) +} + func testAccComposerEnvironment_update(name, network, subnetwork string) string { return fmt.Sprintf(` data "google_composer_image_versions" "all" { diff --git a/website/docs/r/composer_environment.html.markdown b/website/docs/r/composer_environment.html.markdown index 5f5534e37b..5da2166539 100644 --- a/website/docs/r/composer_environment.html.markdown +++ b/website/docs/r/composer_environment.html.markdown @@ -169,6 +169,10 @@ The `config` block supports: (Optional) The configuration used for the Private IP Cloud Composer environment. Structure is documented below. +* `web_server_network_access_control` - + (Optional, [Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html)) + The network-level access control policy for the Airflow web server. If unspecified, no network-level access restrictions will be applied. + The `node_config` block supports: @@ -289,7 +293,7 @@ The `software_config` block supports: The major version of Python used to run the Apache Airflow scheduler, worker, and webserver processes. Can be set to '2' or '3'. If not specified, the default is '2'. Cannot be updated. -The `private_environment_config` block supports: +See [documentation](https://cloud.google.com/composer/docs/how-to/managing/configuring-private-ip) for seting up private environments. The `private_environment_config` block supports: * `enable_private_endpoint` - If true, access to the public endpoint of the GKE cluster is denied. @@ -302,6 +306,32 @@ The `private_environment_config` block supports: in use within the cluster's network. If left blank, the default value of '172.16.0.0/28' is used. +* `cloud_sql_ipv4_cidr_block` - + (Optional) + The CIDR block from which IP range in tenant project will be reserved for Cloud SQL. Needs to be disjoint from `web_server_ipv4_cidr_block` + +* `web_server_ipv4_cidr_block` - + (Optional) + The CIDR block from which IP range for web server will be reserved. Needs to be disjoint from `master_ipv4_cidr_block` and `cloud_sql_ipv4_cidr_block`. + +The `web_server_network_access_control` supports: + +* `allowed_ip_range` - + A collection of allowed IP ranges with descriptions. Structure is documented below. + +The `allowed_ip_range` supports: + +* `value` - + (Required) + IP address or range, defined using CIDR notation, of requests that this rule applies to. + Examples: `192.168.1.1` or `192.168.0.0/16` or `2001:db8::/32` or `2001:0db8:0000:0042:0000:8a2e:0370:7334`. + IP range prefixes should be properly truncated. For example, + `1.2.3.4/24` should be truncated to `1.2.3.0/24`. Similarly, for IPv6, `2001:db8::1/32` should be truncated to `2001:db8::/32`. + +* `description` - + (Optional) + A description of this ip range. + The `ip_allocation_policy` block supports: * `use_ip_aliases` -