github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/google/resource_google_project_iam_policy.go (about)

     1  package google
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"log"
     7  	"sort"
     8  
     9  	"github.com/hashicorp/terraform/helper/schema"
    10  	"google.golang.org/api/cloudresourcemanager/v1"
    11  )
    12  
    13  func resourceGoogleProjectIamPolicy() *schema.Resource {
    14  	return &schema.Resource{
    15  		Create: resourceGoogleProjectIamPolicyCreate,
    16  		Read:   resourceGoogleProjectIamPolicyRead,
    17  		Update: resourceGoogleProjectIamPolicyUpdate,
    18  		Delete: resourceGoogleProjectIamPolicyDelete,
    19  
    20  		Schema: map[string]*schema.Schema{
    21  			"project": &schema.Schema{
    22  				Type:     schema.TypeString,
    23  				Required: true,
    24  				ForceNew: true,
    25  			},
    26  			"policy_data": &schema.Schema{
    27  				Type:             schema.TypeString,
    28  				Required:         true,
    29  				DiffSuppressFunc: jsonPolicyDiffSuppress,
    30  			},
    31  			"authoritative": &schema.Schema{
    32  				Type:     schema.TypeBool,
    33  				Optional: true,
    34  			},
    35  			"etag": &schema.Schema{
    36  				Type:     schema.TypeString,
    37  				Computed: true,
    38  			},
    39  			"restore_policy": &schema.Schema{
    40  				Type:     schema.TypeString,
    41  				Computed: true,
    42  			},
    43  			"disable_project": &schema.Schema{
    44  				Type:     schema.TypeBool,
    45  				Optional: true,
    46  			},
    47  		},
    48  	}
    49  }
    50  
    51  func resourceGoogleProjectIamPolicyCreate(d *schema.ResourceData, meta interface{}) error {
    52  	config := meta.(*Config)
    53  	pid := d.Get("project").(string)
    54  	// Get the policy in the template
    55  	p, err := getResourceIamPolicy(d)
    56  	if err != nil {
    57  		return fmt.Errorf("Could not get valid 'policy_data' from resource: %v", err)
    58  	}
    59  
    60  	// An authoritative policy is applied without regard for any existing IAM
    61  	// policy.
    62  	if v, ok := d.GetOk("authoritative"); ok && v.(bool) {
    63  		log.Printf("[DEBUG] Setting authoritative IAM policy for project %q", pid)
    64  		err := setProjectIamPolicy(p, config, pid)
    65  		if err != nil {
    66  			return err
    67  		}
    68  	} else {
    69  		log.Printf("[DEBUG] Setting non-authoritative IAM policy for project %q", pid)
    70  		// This is a non-authoritative policy, meaning it should be merged with
    71  		// any existing policy
    72  		ep, err := getProjectIamPolicy(pid, config)
    73  		if err != nil {
    74  			return err
    75  		}
    76  
    77  		// First, subtract the policy defined in the template from the
    78  		// current policy in the project, and save the result. This will
    79  		// allow us to restore the original policy at some point (which
    80  		// assumes that Terraform owns any common policy that exists in
    81  		// the template and project at create time.
    82  		rp := subtractIamPolicy(ep, p)
    83  		rps, err := json.Marshal(rp)
    84  		if err != nil {
    85  			return fmt.Errorf("Error marshaling restorable IAM policy: %v", err)
    86  		}
    87  		d.Set("restore_policy", string(rps))
    88  
    89  		// Merge the policies together
    90  		mb := mergeBindings(append(p.Bindings, rp.Bindings...))
    91  		ep.Bindings = mb
    92  		if err = setProjectIamPolicy(ep, config, pid); err != nil {
    93  			return fmt.Errorf("Error applying IAM policy to project: %v", err)
    94  		}
    95  	}
    96  	d.SetId(pid)
    97  	return resourceGoogleProjectIamPolicyRead(d, meta)
    98  }
    99  
   100  func resourceGoogleProjectIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
   101  	log.Printf("[DEBUG]: Reading google_project_iam_policy")
   102  	config := meta.(*Config)
   103  	pid := d.Get("project").(string)
   104  
   105  	p, err := getProjectIamPolicy(pid, config)
   106  	if err != nil {
   107  		return err
   108  	}
   109  
   110  	var bindings []*cloudresourcemanager.Binding
   111  	if v, ok := d.GetOk("restore_policy"); ok {
   112  		var restored cloudresourcemanager.Policy
   113  		// if there's a restore policy, subtract it from the policy_data
   114  		err := json.Unmarshal([]byte(v.(string)), &restored)
   115  		if err != nil {
   116  			return fmt.Errorf("Error unmarshaling restorable IAM policy: %v", err)
   117  		}
   118  		subtracted := subtractIamPolicy(p, &restored)
   119  		bindings = subtracted.Bindings
   120  	} else {
   121  		bindings = p.Bindings
   122  	}
   123  	// we only marshal the bindings, because only the bindings get set in the config
   124  	pBytes, err := json.Marshal(&cloudresourcemanager.Policy{Bindings: bindings})
   125  	if err != nil {
   126  		return fmt.Errorf("Error marshaling IAM policy: %v", err)
   127  	}
   128  	log.Printf("[DEBUG]: Setting etag=%s", p.Etag)
   129  	d.Set("etag", p.Etag)
   130  	d.Set("policy_data", string(pBytes))
   131  	return nil
   132  }
   133  
   134  func resourceGoogleProjectIamPolicyUpdate(d *schema.ResourceData, meta interface{}) error {
   135  	log.Printf("[DEBUG]: Updating google_project_iam_policy")
   136  	config := meta.(*Config)
   137  	pid := d.Get("project").(string)
   138  
   139  	// Get the policy in the template
   140  	p, err := getResourceIamPolicy(d)
   141  	if err != nil {
   142  		return fmt.Errorf("Could not get valid 'policy_data' from resource: %v", err)
   143  	}
   144  	pBytes, _ := json.Marshal(p)
   145  	log.Printf("[DEBUG] Got policy from config: %s", string(pBytes))
   146  
   147  	// An authoritative policy is applied without regard for any existing IAM
   148  	// policy.
   149  	if v, ok := d.GetOk("authoritative"); ok && v.(bool) {
   150  		log.Printf("[DEBUG] Updating authoritative IAM policy for project %q", pid)
   151  		err := setProjectIamPolicy(p, config, pid)
   152  		if err != nil {
   153  			return fmt.Errorf("Error setting project IAM policy: %v", err)
   154  		}
   155  		d.Set("restore_policy", "")
   156  	} else {
   157  		log.Printf("[DEBUG] Updating non-authoritative IAM policy for project %q", pid)
   158  		// Get the previous policy from state
   159  		pp, err := getPrevResourceIamPolicy(d)
   160  		if err != nil {
   161  			return fmt.Errorf("Error retrieving previous version of changed project IAM policy: %v", err)
   162  		}
   163  		ppBytes, _ := json.Marshal(pp)
   164  		log.Printf("[DEBUG] Got previous version of changed project IAM policy: %s", string(ppBytes))
   165  
   166  		// Get the existing IAM policy from the API
   167  		ep, err := getProjectIamPolicy(pid, config)
   168  		if err != nil {
   169  			return fmt.Errorf("Error retrieving IAM policy from project API: %v", err)
   170  		}
   171  		epBytes, _ := json.Marshal(ep)
   172  		log.Printf("[DEBUG] Got existing version of changed IAM policy from project API: %s", string(epBytes))
   173  
   174  		// Subtract the previous and current policies from the policy retrieved from the API
   175  		rp := subtractIamPolicy(ep, pp)
   176  		rpBytes, _ := json.Marshal(rp)
   177  		log.Printf("[DEBUG] After subtracting the previous policy from the existing policy, remaining policies: %s", string(rpBytes))
   178  		rp = subtractIamPolicy(rp, p)
   179  		rpBytes, _ = json.Marshal(rp)
   180  		log.Printf("[DEBUG] After subtracting the remaining policies from the config policy, remaining policies: %s", string(rpBytes))
   181  		rps, err := json.Marshal(rp)
   182  		if err != nil {
   183  			return fmt.Errorf("Error marhsaling restorable IAM policy: %v", err)
   184  		}
   185  		d.Set("restore_policy", string(rps))
   186  
   187  		// Merge the policies together
   188  		mb := mergeBindings(append(p.Bindings, rp.Bindings...))
   189  		ep.Bindings = mb
   190  		if err = setProjectIamPolicy(ep, config, pid); err != nil {
   191  			return fmt.Errorf("Error applying IAM policy to project: %v", err)
   192  		}
   193  	}
   194  
   195  	return resourceGoogleProjectIamPolicyRead(d, meta)
   196  }
   197  
   198  func resourceGoogleProjectIamPolicyDelete(d *schema.ResourceData, meta interface{}) error {
   199  	log.Printf("[DEBUG]: Deleting google_project_iam_policy")
   200  	config := meta.(*Config)
   201  	pid := d.Get("project").(string)
   202  
   203  	// Get the existing IAM policy from the API
   204  	ep, err := getProjectIamPolicy(pid, config)
   205  	if err != nil {
   206  		return fmt.Errorf("Error retrieving IAM policy from project API: %v", err)
   207  	}
   208  	// Deleting an authoritative policy will leave the project with no policy,
   209  	// and unaccessible by anyone without org-level privs. For this reason, the
   210  	// "disable_project" property must be set to true, forcing the user to ack
   211  	// this outcome
   212  	if v, ok := d.GetOk("authoritative"); ok && v.(bool) {
   213  		if v, ok := d.GetOk("disable_project"); !ok || !v.(bool) {
   214  			return fmt.Errorf("You must set 'disable_project' to true before deleting an authoritative IAM policy")
   215  		}
   216  		ep.Bindings = make([]*cloudresourcemanager.Binding, 0)
   217  
   218  	} else {
   219  		// A non-authoritative policy should set the policy to the value of "restore_policy" in state
   220  		// Get the previous policy from state
   221  		rp, err := getRestoreIamPolicy(d)
   222  		if err != nil {
   223  			return fmt.Errorf("Error retrieving previous version of changed project IAM policy: %v", err)
   224  		}
   225  		ep.Bindings = rp.Bindings
   226  	}
   227  	if err = setProjectIamPolicy(ep, config, pid); err != nil {
   228  		return fmt.Errorf("Error applying IAM policy to project: %v", err)
   229  	}
   230  	d.SetId("")
   231  	return nil
   232  }
   233  
   234  // Subtract all bindings in policy b from policy a, and return the result
   235  func subtractIamPolicy(a, b *cloudresourcemanager.Policy) *cloudresourcemanager.Policy {
   236  	am := rolesToMembersMap(a.Bindings)
   237  
   238  	for _, b := range b.Bindings {
   239  		if _, ok := am[b.Role]; ok {
   240  			for _, m := range b.Members {
   241  				delete(am[b.Role], m)
   242  			}
   243  			if len(am[b.Role]) == 0 {
   244  				delete(am, b.Role)
   245  			}
   246  		}
   247  	}
   248  	a.Bindings = rolesToMembersBinding(am)
   249  	return a
   250  }
   251  
   252  func setProjectIamPolicy(policy *cloudresourcemanager.Policy, config *Config, pid string) error {
   253  	// Apply the policy
   254  	pbytes, _ := json.Marshal(policy)
   255  	log.Printf("[DEBUG] Setting policy %#v for project: %s", string(pbytes), pid)
   256  	_, err := config.clientResourceManager.Projects.SetIamPolicy(pid,
   257  		&cloudresourcemanager.SetIamPolicyRequest{Policy: policy}).Do()
   258  
   259  	if err != nil {
   260  		return fmt.Errorf("Error applying IAM policy for project %q. Policy is %#v, error is %s", pid, policy, err)
   261  	}
   262  	return nil
   263  }
   264  
   265  // Get a cloudresourcemanager.Policy from a schema.ResourceData
   266  func getResourceIamPolicy(d *schema.ResourceData) (*cloudresourcemanager.Policy, error) {
   267  	ps := d.Get("policy_data").(string)
   268  	// The policy string is just a marshaled cloudresourcemanager.Policy.
   269  	policy := &cloudresourcemanager.Policy{}
   270  	if err := json.Unmarshal([]byte(ps), policy); err != nil {
   271  		return nil, fmt.Errorf("Could not unmarshal %s:\n: %v", ps, err)
   272  	}
   273  	return policy, nil
   274  }
   275  
   276  // Get the previous cloudresourcemanager.Policy from a schema.ResourceData if the
   277  // resource has changed
   278  func getPrevResourceIamPolicy(d *schema.ResourceData) (*cloudresourcemanager.Policy, error) {
   279  	var policy *cloudresourcemanager.Policy = &cloudresourcemanager.Policy{}
   280  	if d.HasChange("policy_data") {
   281  		v, _ := d.GetChange("policy_data")
   282  		if err := json.Unmarshal([]byte(v.(string)), policy); err != nil {
   283  			return nil, fmt.Errorf("Could not unmarshal previous policy %s:\n: %v", v, err)
   284  		}
   285  	}
   286  	return policy, nil
   287  }
   288  
   289  // Get the restore_policy that can be used to restore a project's IAM policy to its
   290  // state before it was adopted into Terraform
   291  func getRestoreIamPolicy(d *schema.ResourceData) (*cloudresourcemanager.Policy, error) {
   292  	if v, ok := d.GetOk("restore_policy"); ok {
   293  		policy := &cloudresourcemanager.Policy{}
   294  		if err := json.Unmarshal([]byte(v.(string)), policy); err != nil {
   295  			return nil, fmt.Errorf("Could not unmarshal previous policy %s:\n: %v", v, err)
   296  		}
   297  		return policy, nil
   298  	}
   299  	return nil, fmt.Errorf("Resource does not have a 'restore_policy' attribute defined.")
   300  }
   301  
   302  // Retrieve the existing IAM Policy for a Project
   303  func getProjectIamPolicy(project string, config *Config) (*cloudresourcemanager.Policy, error) {
   304  	p, err := config.clientResourceManager.Projects.GetIamPolicy(project,
   305  		&cloudresourcemanager.GetIamPolicyRequest{}).Do()
   306  
   307  	if err != nil {
   308  		return nil, fmt.Errorf("Error retrieving IAM policy for project %q: %s", project, err)
   309  	}
   310  	return p, nil
   311  }
   312  
   313  // Convert a map of roles->members to a list of Binding
   314  func rolesToMembersBinding(m map[string]map[string]bool) []*cloudresourcemanager.Binding {
   315  	bindings := make([]*cloudresourcemanager.Binding, 0)
   316  	for role, members := range m {
   317  		b := cloudresourcemanager.Binding{
   318  			Role:    role,
   319  			Members: make([]string, 0),
   320  		}
   321  		for m, _ := range members {
   322  			b.Members = append(b.Members, m)
   323  		}
   324  		bindings = append(bindings, &b)
   325  	}
   326  	return bindings
   327  }
   328  
   329  // Map a role to a map of members, allowing easy merging of multiple bindings.
   330  func rolesToMembersMap(bindings []*cloudresourcemanager.Binding) map[string]map[string]bool {
   331  	bm := make(map[string]map[string]bool)
   332  	// Get each binding
   333  	for _, b := range bindings {
   334  		// Initialize members map
   335  		if _, ok := bm[b.Role]; !ok {
   336  			bm[b.Role] = make(map[string]bool)
   337  		}
   338  		// Get each member (user/principal) for the binding
   339  		for _, m := range b.Members {
   340  			// Add the member
   341  			bm[b.Role][m] = true
   342  		}
   343  	}
   344  	return bm
   345  }
   346  
   347  // Merge multiple Bindings such that Bindings with the same Role result in
   348  // a single Binding with combined Members
   349  func mergeBindings(bindings []*cloudresourcemanager.Binding) []*cloudresourcemanager.Binding {
   350  	bm := rolesToMembersMap(bindings)
   351  	rb := make([]*cloudresourcemanager.Binding, 0)
   352  
   353  	for role, members := range bm {
   354  		var b cloudresourcemanager.Binding
   355  		b.Role = role
   356  		b.Members = make([]string, 0)
   357  		for m, _ := range members {
   358  			b.Members = append(b.Members, m)
   359  		}
   360  		rb = append(rb, &b)
   361  	}
   362  
   363  	return rb
   364  }
   365  
   366  func jsonPolicyDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
   367  	var oldPolicy, newPolicy cloudresourcemanager.Policy
   368  	if err := json.Unmarshal([]byte(old), &oldPolicy); err != nil {
   369  		log.Printf("[ERROR] Could not unmarshal old policy %s: %v", old, err)
   370  		return false
   371  	}
   372  	if err := json.Unmarshal([]byte(new), &newPolicy); err != nil {
   373  		log.Printf("[ERROR] Could not unmarshal new policy %s: %v", new, err)
   374  		return false
   375  	}
   376  	if newPolicy.Etag != oldPolicy.Etag {
   377  		return false
   378  	}
   379  	if newPolicy.Version != oldPolicy.Version {
   380  		return false
   381  	}
   382  	if len(newPolicy.Bindings) != len(oldPolicy.Bindings) {
   383  		return false
   384  	}
   385  	sort.Sort(sortableBindings(newPolicy.Bindings))
   386  	sort.Sort(sortableBindings(oldPolicy.Bindings))
   387  	for pos, newBinding := range newPolicy.Bindings {
   388  		oldBinding := oldPolicy.Bindings[pos]
   389  		if oldBinding.Role != newBinding.Role {
   390  			return false
   391  		}
   392  		if len(oldBinding.Members) != len(newBinding.Members) {
   393  			return false
   394  		}
   395  		sort.Strings(oldBinding.Members)
   396  		sort.Strings(newBinding.Members)
   397  		for i, newMember := range newBinding.Members {
   398  			oldMember := oldBinding.Members[i]
   399  			if newMember != oldMember {
   400  				return false
   401  			}
   402  		}
   403  	}
   404  	return true
   405  }
   406  
   407  type sortableBindings []*cloudresourcemanager.Binding
   408  
   409  func (b sortableBindings) Len() int {
   410  	return len(b)
   411  }
   412  func (b sortableBindings) Swap(i, j int) {
   413  	b[i], b[j] = b[j], b[i]
   414  }
   415  func (b sortableBindings) Less(i, j int) bool {
   416  	return b[i].Role < b[j].Role
   417  }