Add IAP support for backend services (#471)

* initial work on adding IAP support for backend services

* readback of IAP

* flatten IAP + static set id

* expandIap function

* removed enabled flag/state rework

Removed the enabled flag for IAP
IAP is now enabled when the client id and secret are set
IAP now correctly disables when IAP stanza is removed
Client secret is now correctly hashed against the secret hash stored on the server

* Tests for IAP

* added comments, fixed tabs.

* testing for IAP disabled
This commit is contained in:
Antonio Lobato 2017-10-31 16:27:03 -07:00 committed by Dana Hoffman
parent c882a77db6
commit d37f352f41
2 changed files with 230 additions and 4 deletions

View File

@ -2,6 +2,7 @@ package google
import (
"bytes"
"crypto/sha256"
"errors"
"fmt"
"log"
@ -39,8 +40,35 @@ func resourceComputeBackendService() *schema.Resource {
MaxItems: 1,
},
"iap": &schema.Schema{
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"oauth2_client_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"oauth2_client_secret": &schema.Schema{
Type: schema.TypeString,
Required: true,
Sensitive: true,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
if old == fmt.Sprintf("%x", sha256.Sum256([]byte(new))) {
return true
}
return false
},
},
},
},
},
"backend": &schema.Schema{
Type: schema.TypeSet,
Type: schema.TypeSet,
Optional: true,
Set: resourceGoogleComputeBackendServiceBackendHash,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"group": &schema.Schema{
@ -77,8 +105,6 @@ func resourceComputeBackendService() *schema.Resource {
},
},
},
Optional: true,
Set: resourceGoogleComputeBackendServiceBackendHash,
},
"description": &schema.Schema{
@ -209,6 +235,7 @@ func resourceComputeBackendServiceRead(d *schema.ResourceData, meta interface{})
d.Set("self_link", service.SelfLink)
d.Set("backend", flattenBackends(service.Backends))
d.Set("connection_draining_timeout_sec", service.ConnectionDraining.DrainingTimeoutSec)
d.Set("iap", flattenIap(service.Iap))
d.Set("health_checks", service.HealthChecks)
@ -270,6 +297,31 @@ func resourceComputeBackendServiceDelete(d *schema.ResourceData, meta interface{
return nil
}
func expandIap(configured []interface{}) *compute.BackendServiceIAP {
data := configured[0].(map[string]interface{})
iap := &compute.BackendServiceIAP{
Enabled: true,
Oauth2ClientId: data["oauth2_client_id"].(string),
Oauth2ClientSecret: data["oauth2_client_secret"].(string),
ForceSendFields: []string{"Enabled", "Oauth2ClientId", "Oauth2ClientSecret"},
}
return iap
}
func flattenIap(iap *compute.BackendServiceIAP) []map[string]interface{} {
if iap == nil {
return make([]map[string]interface{}, 1, 1)
}
iapMap := map[string]interface{}{
"enabled": iap.Enabled,
"oauth2_client_id": iap.Oauth2ClientId,
"oauth2_client_secret": iap.Oauth2ClientSecretSha256,
}
return []map[string]interface{}{iapMap}
}
func expandBackends(configured []interface{}) ([]*compute.Backend, error) {
backends := make([]*compute.Backend, 0, len(configured))
@ -323,7 +375,6 @@ func flattenBackends(backends []*compute.Backend) []map[string]interface{} {
data["max_rate"] = b.MaxRate
data["max_rate_per_instance"] = b.MaxRatePerInstance
data["max_utilization"] = b.MaxUtilization
result = append(result, data)
}
@ -337,9 +388,23 @@ func expandBackendService(d *schema.ResourceData) (*compute.BackendService, erro
healthChecks = append(healthChecks, v.(string))
}
// The IAP service is enabled and disabled by adding or removing
// the IAP configuration block (and providing the client id
// and secret). We are force sending the three required API fields
// to enable/disable IAP at all times here, and relying on Golang's
// type defaults to enable or disable IAP in the existance or absense
// of the block, instead of checking if the block exists, zeroing out
// fields, etc.
service := &compute.BackendService{
Name: d.Get("name").(string),
HealthChecks: healthChecks,
Iap: &compute.BackendServiceIAP{
ForceSendFields: []string{"Enabled", "Oauth2ClientId", "Oauth2ClientSecret"},
},
}
if v, ok := d.GetOk("iap"); ok {
service.Iap = expandIap(v.([]interface{}))
}
var err error

View File

@ -120,6 +120,48 @@ func TestAccComputeBackendService_withBackendAndUpdate(t *testing.T) {
}
}
func TestAccComputeBackendService_withBackendAndIAP(t *testing.T) {
serviceName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
igName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
itName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
checkName := fmt.Sprintf("tf-test-%s", acctest.RandString(10))
var svc compute.BackendService
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckComputeBackendServiceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccComputeBackendService_withBackendAndIAP(
serviceName, igName, itName, checkName, 10),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeBackendServiceExistsWithIAP(
"google_compute_backend_service.lipsum", &svc),
),
},
resource.TestStep{
Config: testAccComputeBackendService_withBackend(
serviceName, igName, itName, checkName, 10),
Check: resource.ComposeTestCheckFunc(
testAccCheckComputeBackendServiceExistsWithoutIAP(
"google_compute_backend_service.lipsum", &svc),
),
},
},
})
if svc.TimeoutSec != 10 {
t.Errorf("Expected TimeoutSec == 10, got %d", svc.TimeoutSec)
}
if svc.Protocol != "HTTP" {
t.Errorf("Expected Protocol to be HTTP, got %q", svc.Protocol)
}
if len(svc.Backends) != 1 {
t.Errorf("Expected 1 backend, got %d", len(svc.Backends))
}
}
func TestAccComputeBackendService_updatePreservesOptionalParameters(t *testing.T) {
t.Parallel()
@ -287,6 +329,71 @@ func testAccCheckComputeBackendServiceExists(n string, svc *compute.BackendServi
}
}
func testAccCheckComputeBackendServiceExistsWithIAP(n string, svc *compute.BackendService) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
config := testAccProvider.Meta().(*Config)
found, err := config.clientCompute.BackendServices.Get(
config.Project, rs.Primary.ID).Do()
if err != nil {
return err
}
if found.Name != rs.Primary.ID {
return fmt.Errorf("Backend service %s not found", rs.Primary.ID)
}
if found.Iap == nil || found.Iap.Enabled == false {
return fmt.Errorf("IAP not found or not enabled.")
}
*svc = *found
return nil
}
}
func testAccCheckComputeBackendServiceExistsWithoutIAP(n string, svc *compute.BackendService) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No ID is set")
}
config := testAccProvider.Meta().(*Config)
found, err := config.clientCompute.BackendServices.Get(
config.Project, rs.Primary.ID).Do()
if err != nil {
return err
}
if found.Name != rs.Primary.ID {
return fmt.Errorf("Backend service %s not found", rs.Primary.ID)
}
if found.Iap != nil && found.Iap.Enabled == true {
return fmt.Errorf("IAP enabled when it should be disabled")
}
*svc = *found
return nil
}
}
func TestAccComputeBackendService_withCDNEnabled(t *testing.T) {
t.Parallel()
@ -460,6 +567,60 @@ resource "google_compute_http_health_check" "default" {
`, serviceName, timeout, igName, itName, checkName)
}
func testAccComputeBackendService_withBackendAndIAP(
serviceName, igName, itName, checkName string, timeout int64) string {
return fmt.Sprintf(`
resource "google_compute_backend_service" "lipsum" {
name = "%s"
description = "Hello World 1234"
port_name = "http"
protocol = "HTTP"
timeout_sec = %v
backend {
group = "${google_compute_instance_group_manager.foobar.instance_group}"
}
iap {
oauth2_client_id = "test"
oauth2_client_secret = "test"
}
health_checks = ["${google_compute_http_health_check.default.self_link}"]
}
resource "google_compute_instance_group_manager" "foobar" {
name = "%s"
instance_template = "${google_compute_instance_template.foobar.self_link}"
base_instance_name = "foobar"
zone = "us-central1-f"
target_size = 1
}
resource "google_compute_instance_template" "foobar" {
name = "%s"
machine_type = "n1-standard-1"
network_interface {
network = "default"
}
disk {
source_image = "debian-8-jessie-v20160803"
auto_delete = true
boot = true
}
}
resource "google_compute_http_health_check" "default" {
name = "%s"
request_path = "/"
check_interval_sec = 1
timeout_sec = 1
}
`, serviceName, timeout, igName, itName, checkName)
}
func testAccComputeBackendService_withSessionAffinity(serviceName, checkName, description, affinityName string) string {
return fmt.Sprintf(`
resource "google_compute_backend_service" "foobar" {