Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Google Workspace Provider Init #1396

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
47 changes: 47 additions & 0 deletions cmd/provider_cmd_googleworkspace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package cmd

import (
"errors"
"os"

googleworkspace_terraforming "github.com/GoogleCloudPlatform/terraformer/providers/googleworkspace"
"github.com/GoogleCloudPlatform/terraformer/terraformutils"
"github.com/spf13/cobra"
)

func newCmdGoogleWorkspaceImporter(options ImportOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "googleworkspace",
Short: "Import current State to terraform configuration from Google Workspace",
Long: "Import current State to terraform configuration from Google Workspace",
RunE: func(cmd *cobra.Command, args []string) error {
orgID := os.Getenv("GOOGLEWORKSPACE_CUSTOMER_ID")
if len(orgID) == 0 {
return errors.New("Google Workspace Org ID must be set through the `GOOGLEWORKSPACE_ORG_ID` env var")
}
credentialJson := os.Getenv("GOOGLEWORKSPACE_CREDENTIALS")
if len(credentialJson) == 0 {
return errors.New("Path to the credential JSON file for the Google Workspace Service Account must be set through the `GOOGLEWORKSPACE_CREDENTIALS` env var")
}

impersonatedUserEmail := os.Getenv("GOOGLEWORKSPACE_IMPERSONATED_USER_EMAIL")
if len(impersonatedUserEmail) == 0 {
return errors.New("Email address of a user to impersonate for Google Admin actions must be set through the `GOOGLEWORKSPACE_IMPERSONATED_USER_EMAIL` env var")
}

provider := newGoogleWorkspaceProvider()
err := Import(provider, options, []string{orgID, credentialJson, impersonatedUserEmail})
if err != nil {
return err
}
return nil
},
}
cmd.AddCommand(listCmd(newGoogleWorkspaceProvider()))
baseProviderFlags(cmd.PersistentFlags(), &options, "bleh", "bleh=bleh1,i don't know what this does")
return cmd
}

func newGoogleWorkspaceProvider() terraformutils.ProviderGenerator {
return &googleworkspace_terraforming.GoogleWorkspaceProvider{}
}
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ func providerImporterSubcommands() []func(options ImportOptions) *cobra.Command
newCmdVaultImporter,
newCmdOktaImporter,
newCmdAuth0Importer,
newCmdGoogleWorkspaceImporter,
}
}

Expand Down Expand Up @@ -133,6 +134,7 @@ func providerGenerators() map[string]func() terraformutils.ProviderGenerator {
newVaultProvider,
newOktaProvider,
newAuth0Provider,
newGoogleWorkspaceProvider,
} {
list[providerGen().GetName()] = providerGen
}
Expand Down
17 changes: 17 additions & 0 deletions docs/googleworkspace.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
### Use with Google Workspace

Example:

```
$ export GOOGLEWORKSPACE_CUSTOMER_ID=<CUSTOMER_ID>
$ export GOOGLEWORKSPACE_CREDENTIALS=</PATH/TO/CREDENTIALS.json>
$ export GOOGLEWORKSPACE_IMPERSONATED_USER_EMAIL=<[email protected]>
$ terraformer import googleworkspace --resources=org_unit,chrome_policy
```

List of supported Google Workspace resources:

* `Directory API` - [Reference](https://developers.google.com/admin-sdk/directory/reference/rest)
* `org_unit` - [orgunits](https://developers.google.com/admin-sdk/directory/reference/rest/v1/orgunits)
* `Chrome Policy API` - [Reference](https://developers.google.com/chrome/policy/reference/rest)
* `chrome_policy` - [policies](https://developers.google.com/chrome/policy/reference/rest/v1/customers.policies.orgunits/batchModify)
142 changes: 142 additions & 0 deletions providers/googleworkspace/chrome_policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package googleworkspace

import (
"context"
"encoding/json"
"log"
"strings"
"time"

"github.com/GoogleCloudPlatform/terraformer/terraformutils"
"google.golang.org/api/chromepolicy/v1"
)

type ChromePolicyGenerator struct {
GoogleWorkspaceService
}

func (g *ChromePolicyGenerator) InitResources() error {
client, err := g.ChromePolicyClient()
if err != nil {
return err
}

var policySchemaList []*chromepolicy.GoogleChromePolicyV1PolicySchema
policySchemaListResponse, err := client.Customers.PolicySchemas.List("customers/" + g.orgID).Do()
if err != nil {
return err
}
policySchemaList = append(policySchemaList, policySchemaListResponse.PolicySchemas...)
for {
if policySchemaListResponse.NextPageToken == "" {
break
}
policySchemaListResponse, err = client.Customers.PolicySchemas.List("customers/" + g.orgID).PageToken(policySchemaListResponse.NextPageToken).Do()
if err != nil {
return err
}
policySchemaList = append(policySchemaList, policySchemaListResponse.PolicySchemas...)

}

policySchemaLeafs := map[string]*struct{}{}
found := &struct{}{}
for _, policySchema := range policySchemaList {
splitSchema := strings.Split(policySchema.SchemaName, ".")
splitSchema = splitSchema[:len(splitSchema)-1]
schemaLeaf := strings.Join(splitSchema, ".")
if _, alreadyFound := policySchemaLeafs[schemaLeaf]; !alreadyFound {
policySchemaLeafs[schemaLeaf] = found
}
}

orgUnitList, err := g.getAllOrgUnits()
if err != nil {
return err
}

for _, orgUnit := range orgUnitList {
log.Println("Loading " + orgUnit.OrgUnitPath)
orgUnitPolicies := []*chromepolicy.GoogleChromePolicyV1ResolvedPolicy{}
for policySchemaLeaf, _ := range policySchemaLeafs {
policyTargetOrgUnit := &chromepolicy.GoogleChromePolicyV1PolicyTargetKey{
TargetResource: "orgunits/" + strings.Split(orgUnit.OrgUnitId, ":")[1],
}
err := retryTimeDuration(context.Background(), time.Minute, func() error {
return client.Customers.Policies.Resolve("customers/"+g.orgID, &chromepolicy.GoogleChromePolicyV1ResolveRequest{
PolicySchemaFilter: policySchemaLeaf + ".*",
PolicyTargetKey: policyTargetOrgUnit,
}).Pages(context.Background(), func(chromePolicyResponse *chromepolicy.GoogleChromePolicyV1ResolveResponse) error {
orgUnitPolicies = append(orgUnitPolicies, chromePolicyResponse.ResolvedPolicies...)
return nil
})
})
if err != nil {
return err
}
}
resource := g.createOrgUnitResource(strings.Split(orgUnit.OrgUnitId, ":")[1], orgUnit.Name, orgUnitPolicies)
if resource != nil {
g.Resources = append(g.Resources, *resource)
}
}
return nil
}

type chromePolicySchema struct {
SchemaName string `json:"schema_name,omitempty"`
SchemaValues map[string]interface{} `json:"schema_values,omitempty"`
}

func (g ChromePolicyGenerator) createOrgUnitResource(orgUnitID string, orgUnitName string, chromePolicies []*chromepolicy.GoogleChromePolicyV1ResolvedPolicy) *terraformutils.Resource {
if len(chromePolicies) == 0 {
return nil
}

var policySchemas []chromePolicySchema
for _, chromePolicy := range chromePolicies {

// Skipping inherited policies
if chromePolicy.SourceKey.TargetResource != chromePolicy.TargetKey.TargetResource {
continue
}

chromePolicyDefinition := chromePolicySchema{
SchemaName: chromePolicy.Value.PolicySchema,
}

valueBytes, err := chromePolicy.Value.Value.MarshalJSON()
if err != nil {
log.Fatal("Failed to marshal Chrome Policy Value definition", err)
return nil
}

err = json.Unmarshal(valueBytes, &chromePolicyDefinition.SchemaValues)
if err != nil {
log.Fatal("Failed to Unmarshal Chrome Policy Value definition", err)
return nil
}

policySchemas = append(policySchemas, chromePolicyDefinition)
}

if len(policySchemas) == 0 {
return nil
}

resourceName := g.EnsureStringRandomness("chrome_policy_" + orgUnitName)
resource := terraformutils.NewResource(
orgUnitID,
resourceName,
"googleworkspace_chrome_policy",
"googleworkspace",
map[string]string{},
[]string{},
map[string]interface{}{
"org_unit_id": orgUnitID,
"policies": policySchemas,
},
)

return &resource
}
91 changes: 91 additions & 0 deletions providers/googleworkspace/googleworkspace_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package googleworkspace

import (
"errors"
"os"

"github.com/GoogleCloudPlatform/terraformer/terraformutils"
"github.com/GoogleCloudPlatform/terraformer/terraformutils/providerwrapper"
)

type GoogleWorkspaceProvider struct {
terraformutils.Provider
orgID string
credentialJsonFilepath string
impersonatedUserEmail string
}

func (p *GoogleWorkspaceProvider) GetProviderData(arg ...string) map[string]interface{} {
return map[string]interface{}{
"provider": map[string]interface{}{
p.GetName(): map[string]interface{}{
"customer_id": p.orgID,
},
},
"terraform": map[string]interface{}{
"required_providers": []map[string]interface{}{{
p.GetName(): map[string]interface{}{
"source": "yohan460/googleworkspace",
"version": providerwrapper.GetProviderVersion(p.GetName()),
},
}},
},
}
}

func (p *GoogleWorkspaceProvider) Init(args []string) error {
orgID := os.Getenv("GOOGLEWORKSPACE_CUSTOMER_ID")
if orgID == "" {
return errors.New("set GOOGLEWORKSPACE_CUSTOMER_ID env var")
}
p.orgID = orgID

credentialJsonFilepath := os.Getenv("GOOGLEWORKSPACE_CREDENTIALS")
if credentialJsonFilepath == "" {
return errors.New("set GOOGLEWORKSPACE_CREDENTIALS env var")
}
p.credentialJsonFilepath = credentialJsonFilepath

impersonatedUserEmail := os.Getenv("GOOGLEWORKSPACE_IMPERSONATED_USER_EMAIL")
if impersonatedUserEmail == "" {
return errors.New("set GOOGLEWORKSPACE_IMPERSONATED_USER_EMAIL env var")
}
p.impersonatedUserEmail = impersonatedUserEmail

return nil
}

func (p *GoogleWorkspaceProvider) GetResourceConnections() map[string]map[string][]string {
return map[string]map[string][]string{
"alerts": {"alert_notification_endpoints": []string{"alert_notification_endpoints", "id"}},
}
}

func (p *GoogleWorkspaceProvider) GetName() string {
return "googleworkspace"
}

func (p *GoogleWorkspaceProvider) InitService(serviceName string, verbose bool) error {
var isSupported bool
fullname := p.GetName() + "_" + serviceName
if _, isSupported = p.GetSupportedService()[fullname]; !isSupported {
return errors.New(p.GetName() + ": " + serviceName + " is not a supported service")
}
p.Service = p.GetSupportedService()[fullname]
p.Service.SetName(fullname)
p.Service.SetProviderName(p.GetName())
p.Service.SetVerbose(verbose)
p.Service.SetArgs(map[string]interface{}{
"org_id": p.orgID,
"credential_json_filepath": p.credentialJsonFilepath,
"impersonated_user_email": p.impersonatedUserEmail,
})
return nil
}

func (p *GoogleWorkspaceProvider) GetSupportedService() map[string]terraformutils.ServiceGenerator {
return map[string]terraformutils.ServiceGenerator{
"googleworkspace_org_unit": &OrgUnitGenerator{},
"googleworkspace_chrome_policy": &ChromePolicyGenerator{},
}
}