diff --git a/build/terraform b/build/terraform index 8623db4dbbbd..188ef94e612b 160000 --- a/build/terraform +++ b/build/terraform @@ -1 +1 @@ -Subproject commit 8623db4dbbbd085d3d92e0d56d113885c55a5aba +Subproject commit 188ef94e612bb50914c625bbd9c8d3807a0fae16 diff --git a/build/terraform-beta b/build/terraform-beta index 52bfa4d37736..eb5a485e757b 160000 --- a/build/terraform-beta +++ b/build/terraform-beta @@ -1 +1 @@ -Subproject commit 52bfa4d37736c71ea423b003c7227739399fa5d8 +Subproject commit eb5a485e757bf38d6eaab23ebb1b896c5d9c6f1d diff --git a/products/accesscontextmanager/api.yaml b/products/accesscontextmanager/api.yaml index 050c596672e6..21783b679085 100644 --- a/products/accesscontextmanager/api.yaml +++ b/products/accesscontextmanager/api.yaml @@ -260,3 +260,154 @@ objects: - :DESKTOP_CHROME_OS - :ANDROID - :IOS + - !ruby/object:Api::Resource + name: 'ServicePerimeter' + # This is an unusual API, so we need to use a few fields to map the methods + # to the right URL. + # create_url is the Create URL + # base_url is the Get and Delete and Patch URL. It is empty on purpose. + # List won't work yet. It should share a URL with Create. + create_url: "{{parent}}/servicePerimeters" + base_url: "" + self_link: "{{name}}" + update_verb: :PATCH + references: !ruby/object:Api::Resource::ReferenceLinks + guides: + 'Access Policy Quickstart': 'https://cloud.google.com/access-context-manager/docs/quickstart' + api: 'https://cloud.google.com/access-context-manager/docs/reference/rest/v1beta/accessPolicies.servicePerimeters' + description: | + ServicePerimeter describes a set of GCP resources which can freely import + and export data amongst themselves, but not export outside of the + ServicePerimeter. If a request with a source within this ServicePerimeter + has a target outside of the ServicePerimeter, the request will be blocked. + Otherwise the request is allowed. There are two types of Service Perimeter + - Regular and Bridge. Regular Service Perimeters cannot overlap, a single + GCP project can only belong to a single regular Service Perimeter. Service + Perimeter Bridges can contain only GCP projects as members, a single GCP + project may belong to multiple Service Perimeter Bridges. +<%= indent(compile_file({}, 'templates/global_async.yaml.erb'), 4) %> + parameters: + # Parent is a path parameter that _cannot_ be read or sent in the request at all. + # This must be done at the provider level. + - !ruby/object:Api::Type::String + name: parent + input: true + required: true + description: | + The AccessPolicy this ServicePerimeter lives in. + Format: accessPolicies/{policy_id} + - !ruby/object:Api::Type::String + name: name + input: true + required: true + description: | + Resource name for the ServicePerimeter. The short_name component must + begin with a letter and only include alphanumeric and '_'. + Format: accessPolicies/{policy_id}/servicePerimeters/{short_name} + properties: + - !ruby/object:Api::Type::String + name: title + required: true + description: | + Human readable title. Must be unique within the Policy. + - !ruby/object:Api::Type::String + name: 'description' + description: | + Description of the ServicePerimeter and its use. Does not affect + behavior. + - !ruby/object:Api::Type::Time + name: 'createTime' + description: | + Time the AccessPolicy was created in UTC. + output: true + - !ruby/object:Api::Type::Time + name: 'updateTime' + description: | + Time the AccessPolicy was updated in UTC. + output: true + - !ruby/object:Api::Type::Enum + name: 'perimeterType' + description: | + Specifies the type of the Perimeter. There are two types: regular and + bridge. Regular Service Perimeter contains resources, access levels, + and restricted/unrestricted services. Every resource can be in at most + ONE regular Service Perimeter. + + In addition to being in a regular service perimeter, a resource can also + be in zero or more perimeter bridges. A perimeter bridge only contains + resources. Cross project operations are permitted if all effected + resources share some perimeter (whether bridge or regular). Perimeter + Bridge does not contain access levels or services: those are governed + entirely by the regular perimeter that resource is in. + + Perimeter Bridges are typically useful when building more complex + toplogies with many independent perimeters that need to share some data + with a common perimeter, but should not be able to share data among + themselves. + values: + - :PERIMETER_TYPE_REGULAR + - :PERIMETER_TYPE_BRIDGE + default_value: :PERIMETER_TYPE_REGULAR + - !ruby/object:Api::Type::NestedObject + name: 'status' + description: | + ServicePerimeter configuration. Specifies sets of resources, + restricted/unrestricted services and access levels that determine + perimeter content and boundaries. + properties: + - !ruby/object:Api::Type::Array + name: 'resources' + description: | + A list of GCP resources that are inside of the service perimeter. + Currently only projects are allowed. + Format: projects/{project_number} + item_type: Api::Type::String + - !ruby/object:Api::Type::Array + name: 'accessLevels' + description: | + A list of AccessLevel resource names that allow resources within + the ServicePerimeter to be accessed from the internet. + AccessLevels listed must be in the same policy as this + ServicePerimeter. Referencing a nonexistent AccessLevel is a + syntax error. If no AccessLevel names are listed, resources within + the perimeter can only be accessed via GCP calls with request + origins within the perimeter. For Service Perimeter Bridge, must + be empty. + + Format: accessPolicies/{policy_id}/accessLevels/{access_level_name} + item_type: Api::Type::String + - !ruby/object:Api::Type::Array + name: 'unrestrictedServices' + description: | + GCP services that are not subject to the Service Perimeter + restrictions. May contain a list of services or a single wildcard + "*". For example, if logging.googleapis.com is unrestricted, users + can access logs inside the perimeter as if the perimeter doesn't + exist, and it also means VMs inside the perimeter can access logs + outside the perimeter. + + The wildcard means that unless explicitly specified by + "restrictedServices" list, any service is treated as unrestricted. + One of the fields "restrictedServices", "unrestrictedServices" + must contain a wildcard "*", otherwise the Service Perimeter + specification is invalid. It also means that both field being + empty is invalid as well. "unrestrictedServices" can be empty if + and only if "restrictedServices" list contains a "*" wildcard. + item_type: Api::Type::String + - !ruby/object:Api::Type::Array + name: 'restrictedServices' + description: | + GCP services that are subject to the Service Perimeter + restrictions. May contain a list of services or a single wildcard + "*". For example, if storage.googleapis.com is specified, access + to the storage buckets inside the perimeter must meet the + perimeter's access restrictions. + + Wildcard means that unless explicitly specified by + "unrestrictedServices" list, any service is treated as restricted. + One of the fields "restrictedServices", "unrestrictedServices" + must contain a wildcard "*", otherwise the Service Perimeter + specification is invalid. It also means that both field being + empty is invalid as well. "restrictedServices" can be empty if and + only if "unrestrictedServices" list contains a "*" wildcard. + item_type: Api::Type::String diff --git a/products/accesscontextmanager/terraform.yaml b/products/accesscontextmanager/terraform.yaml index cc52a794842a..179f207ff796 100644 --- a/products/accesscontextmanager/terraform.yaml +++ b/products/accesscontextmanager/terraform.yaml @@ -50,6 +50,28 @@ overrides: !ruby/object:Provider::ResourceOverrides pre_update: templates/terraform/pre_update/update_mask.erb encoder: templates/terraform/encoders/access_level_never_send_parent.go.erb custom_import: templates/terraform/custom_import/access_level_self_link_as_name_and_set_parent.go.erb + ServicePerimeter: !ruby/object:Provider::Terraform::ResourceOverride + id_format: "{{name}}" + import_format: ["{{name}}"] + example: + - !ruby/object:Provider::Terraform::Examples + name: "access_context_manager_service_perimeter_basic" + skip_test: true + primary_resource_id: "service-perimeter" + version: <%= version_name %> + vars: + access_level_name: "ios_no_lock" + service_perimeter_name: "restrict_all" + properties: + parent: !ruby/object:Provider::Terraform::PropertyOverride + ignore_read: true + perimeterType: !ruby/object:Provider::Terraform::PropertyOverride + custom_flatten: templates/terraform/custom_flatten/default_if_empty.erb + input: true + custom_code: !ruby/object:Provider::Terraform::CustomCode + pre_update: templates/terraform/pre_update/update_mask.erb + encoder: templates/terraform/encoders/access_level_never_send_parent.go.erb + custom_import: templates/terraform/custom_import/access_level_self_link_as_name_and_set_parent.go.erb # This is for copying files over files: !ruby/object:Provider::Config::Files # These files have templating (ERB) code that will be run. diff --git a/templates/terraform/examples/access_context_manager_service_perimeter_basic.tf.erb b/templates/terraform/examples/access_context_manager_service_perimeter_basic.tf.erb new file mode 100644 index 000000000000..2592b845c196 --- /dev/null +++ b/templates/terraform/examples/access_context_manager_service_perimeter_basic.tf.erb @@ -0,0 +1,29 @@ +resource "google_access_context_manager_service_perimeter" "<%= ctx[:primary_resource_id] %>" { + parent = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}" + name = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}/servicePerimeters/<%= ctx[:vars]['service_perimeter_name'] %>" + title = "<%= ctx[:vars]['service_perimeter_name'] %>" + status { + restricted_services = ["*"] + } +} + +resource "google_access_context_manager_access_level" "access-level" { + parent = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}" + name = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}/accessLevels/<%= ctx[:vars]['access_level_name'] %>" + title = "<%= ctx[:vars]['access_level_name'] %>" + basic { + conditions { + device_policy { + require_screen_lock = false + os_constraints { + os_type = "IOS" + } + } + } + } +} + +resource "google_access_context_manager_access_policy" "access-policy" { + parent = "organizations/123456789" + title = "my policy" +} diff --git a/third_party/terraform/tests/resource_access_context_manager_service_perimeter_test.go.erb b/third_party/terraform/tests/resource_access_context_manager_service_perimeter_test.go.erb new file mode 100644 index 000000000000..0725ac79a001 --- /dev/null +++ b/third_party/terraform/tests/resource_access_context_manager_service_perimeter_test.go.erb @@ -0,0 +1,180 @@ +<% autogen_exception -%> +package google +<% unless version.nil? || version == 'ga' -%> + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +// Access Context Manager tests need to run serially +func TestAccAccessContextManagerServicePerimeter_basic(t *testing.T) { + org := getTestOrgFromEnv(t) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAccessContextManagerServicePerimeterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAccessContextManagerServicePerimeter_basic(org, "my policy", "level", "perimeter"), + }, + { + ResourceName: "google_access_context_manager_service_perimeter.test-access", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAccessContextManagerServicePerimeter_update(t *testing.T) { + org := getTestOrgFromEnv(t) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAccessContextManagerServicePerimeterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAccessContextManagerServicePerimeter_update(org, "my policy", "level", "perimeter"), + }, + { + ResourceName: "google_access_context_manager_service_perimeter.test-access", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAccessContextManagerServicePerimeter_update2(org, "my policy", "level", "perimeter"), + }, + { + ResourceName: "google_access_context_manager_service_perimeter.test-access", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAccessContextManagerServicePerimeterDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_access_context_manager_service_perimeter" { + continue + } + + config := testAccProvider.Meta().(*Config) + + url, err := replaceVarsForTest(rs, "https://accesscontextmanager.googleapis.com/v1beta/{{name}}") + if err != nil { + return err + } + + _, err = sendRequest(config, "GET", url, nil) + if err == nil { + return fmt.Errorf("ServicePerimeter still exists at %s", url) + } + } + + return nil +} + +func testAccAccessContextManagerServicePerimeter_basic(org, policyTitle, levelTitleName, perimeterTitleName string) string { + return fmt.Sprintf(` +resource "google_access_context_manager_access_policy" "test-access" { + parent = "organizations/%s" + title = "%s" +} + +resource "google_access_context_manager_access_level" "test-access" { + parent = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}" + name = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}/accessLevels/%s" + title = "%s" + description = "hello" + basic { + combining_function = "AND" + conditions { + ip_subnetworks = ["192.0.4.0/24"] + } + } +} + +resource "google_access_context_manager_service_perimeter" "test-access" { + parent = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}" + name = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}/servicePerimeters/%s" + title = "%s" + perimeter_type = "PERIMETER_TYPE_BRIDGE" +} +`, org, policyTitle, levelTitleName, levelTitleName, perimeterTitleName, perimeterTitleName) +} + +func testAccAccessContextManagerServicePerimeter_update(org, policyTitle, levelTitleName, perimeterTitleName string) string { + return fmt.Sprintf(` +resource "google_access_context_manager_access_policy" "test-access" { + parent = "organizations/%s" + title = "%s" +} + +resource "google_access_context_manager_access_level" "test-access" { + parent = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}" + name = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}/accessLevels/%s" + title = "%s" + description = "hello" + basic { + combining_function = "AND" + conditions { + ip_subnetworks = ["192.0.4.0/24"] + } + } +} + +resource "google_access_context_manager_service_perimeter" "test-access" { + parent = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}" + name = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}/servicePerimeters/%s" + title = "%s" + perimeter_type = "PERIMETER_TYPE_REGULAR" + status { + restricted_services = ["*"] + } +} +`, org, policyTitle, levelTitleName, levelTitleName, perimeterTitleName, perimeterTitleName) +} + +func testAccAccessContextManagerServicePerimeter_update2(org, policyTitle, levelTitleName, perimeterTitleName string) string { + return fmt.Sprintf(` +resource "google_access_context_manager_access_policy" "test-access" { + parent = "organizations/%s" + title = "%s" +} + +resource "google_access_context_manager_access_level" "test-access" { + parent = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}" + name = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}/accessLevels/%s" + title = "%s" + description = "hello" + basic { + combining_function = "AND" + conditions { + ip_subnetworks = ["192.0.4.0/24"] + } + } +} + +resource "google_access_context_manager_service_perimeter" "test-access" { + parent = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}" + name = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}/servicePerimeters/%s" + title = "%s" + perimeter_type = "PERIMETER_TYPE_REGULAR" + status { + unrestricted_services = ["*"] + } +} +`, org, policyTitle, levelTitleName, levelTitleName, perimeterTitleName, perimeterTitleName) +} + +<% else %> +// Magic Modules doesn't let us remove files - blank out beta-only common-compile files for now. +<% end -%> +