Skip to content

Commit

Permalink
feat(resource): new lacework_agent_access_token
Browse files Browse the repository at this point in the history
** lacework_agent_access_token

To connect to the Lacework platform, Lacework agents require an agent access token. Use this resource to
mange agent tokens within your Lacework account.

!> **Warning:** Agent tokens should be treated as secret and not published. A token uniquely identifies
a Lacework customer. If you suspect your token has been publicly exposed or compromised, generate a new
token, update the new token on all machines using the old token. When complete, the old token can safely
be disabled without interrupting Lacework services.

You can use the agent token name to logically separate your deployments, for example, by environment types
(QA, Dev, etc.) or system types (CentOS, RHEL, etc.).

-> **Note:** The Lacework agent runs on most Linux distributions. For more detailed information, see
	[Supported Operating Systems.](https://support.lacework.com/hc/en-us/articles/360005230014-Supported-Operating-Systems).

!> **Warning:** By design, agent tokens cannot be deleted. Running terraform destroy will only disable the token.

** Example Usage

```hcl
resource "lacework_agent_access_token" "k8s" {
  name        = "prod"
  description = "k8s deployment for production env"
}
```

** Argument Reference

The following arguments are supported:

* `name` - (Required) The agent access token name.
* `description` - (Optional) The agent access token description.
* `enabled` - (Optional) The state of the external integration. Defaults to `true`.

** Attributes Reference

In addition to the arguments listed above, the following computed attributes are exported:

* `token` - The agent access token.

** Import

A Lacework agent access token can be imported using the token itself, e.g.

```
$ terraform import lacework_agent_access_token.k8s YourAgentToken
```
-> **Note:** To list all agent access tokens in your Lacework account, use the
	Lacework CLI command `lacework agent token list`. To install this tool follow
	[this documentation](https://github.com/lacework/go-sdk/wiki/CLI-Documentation#installation).

Closes #41

Signed-off-by: Salim Afiune Maya <[email protected]>
  • Loading branch information
afiune committed Dec 8, 2020
1 parent e5c6ae0 commit 564c670
Show file tree
Hide file tree
Showing 506 changed files with 92,068 additions and 807 deletions.
6 changes: 6 additions & 0 deletions examples/resource_lacework_agent_access_token/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
provider "lacework" {}

resource "lacework_agent_access_token" "k8s" {
name = "k8s-deployments"
description = "Token for K8S clusters"
}
10 changes: 3 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,22 @@ module github.com/lacework/terraform-provider-lacework
go 1.14

require (
cloud.google.com/go v0.60.0 // indirect
cloud.google.com/go/storage v1.10.0 // indirect
github.com/BurntSushi/toml v0.3.1
github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-cidr v1.1.0 // indirect
github.com/aws/aws-sdk-go v1.33.3 // indirect
github.com/hashicorp/go-getter v1.4.1 // indirect
github.com/hashicorp/go-hclog v0.14.1 // indirect
github.com/hashicorp/go-multierror v1.1.0 // indirect
github.com/hashicorp/go-plugin v1.3.0 // indirect
github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/hashicorp/go-version v1.2.1 // indirect
github.com/hashicorp/hcl/v2 v2.6.0 // indirect
github.com/hashicorp/terraform-plugin-sdk v1.15.0
github.com/hashicorp/terraform-plugin-sdk v1.16.0
github.com/hashicorp/terraform-plugin-test v1.4.3 // indirect
github.com/hashicorp/terraform-svchost v0.0.0-20191119180714-d2e4933b9136 // indirect
github.com/hashicorp/yamux v0.0.0-20200609203250-aecfd211c9ce // indirect
github.com/lacework/go-sdk v0.2.10-0.20201202210122-3d71ed1dcb01
github.com/lacework/go-sdk v0.2.10
github.com/mattn/go-colorable v0.1.7 // indirect
github.com/mitchellh/cli v1.1.1 // indirect
github.com/mitchellh/go-homedir v1.1.0
Expand All @@ -36,11 +35,8 @@ require (
github.com/zclconf/go-cty-yaml v1.0.2 // indirect
go.opencensus.io v0.22.4 // indirect
go.uber.org/zap v1.15.0 // indirect
golang.org/x/crypto v0.0.0-20200707235045-ab33eee955e0 // indirect
golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect
golang.org/x/text v0.3.3 // indirect
golang.org/x/tools v0.0.0-20200708003708-134513de8882 // indirect
google.golang.org/genproto v0.0.0-20200708133552-18036109789b // indirect
google.golang.org/grpc v1.30.0 // indirect
)
61 changes: 61 additions & 0 deletions go.sum

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lacework/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func Provider() terraform.ResourceProvider {
},

ResourcesMap: map[string]*schema.Resource{
"lacework_agent_access_token": resourceLaceworkAgentAccessToken(),
"lacework_alert_channel_aws_cloudwatch": resourceLaceworkAlertChannelAwsCloudWatch(),
"lacework_alert_channel_jira_cloud": resourceLaceworkAlertChannelJiraCloud(),
"lacework_alert_channel_jira_server": resourceLaceworkAlertChannelJiraServer(),
Expand Down
23 changes: 23 additions & 0 deletions lacework/rand.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package lacework

import (
"math/rand"
"time"
)

var (
charset = "abcdefghijklmnopqrstuvwxyz0123456789"
randomSeed *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano()))
)

func randomString(length int) string {
return stringFromCharset(length, charset)
}

func stringFromCharset(length int, charset string) string {
bytes := make([]byte, length)
for i := range bytes {
bytes[i] = charset[randomSeed.Intn(len(charset))]
}
return string(bytes)
}
250 changes: 250 additions & 0 deletions lacework/resource_lacework_agent_access_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
package lacework

import (
"fmt"
"log"
"time"

"github.com/hashicorp/terraform-plugin-sdk/helper/schema"

"github.com/lacework/go-sdk/api"
)

func resourceLaceworkAgentAccessToken() *schema.Resource {
return &schema.Resource{
Create: resourceLaceworkAgentAccessTokenCreate,
Read: resourceLaceworkAgentAccessTokenRead,
Update: resourceLaceworkAgentAccessTokenUpdate,
Delete: resourceLaceworkAgentAccessTokenDelete,

Importer: &schema.ResourceImporter{
State: importLaceworkAgentAccessToken,
},

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"description": {
Type: schema.TypeString,
Optional: true,
},
"enabled": {
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"last_updated_time": {
Type: schema.TypeString,
Computed: true,
},
"created_time": {
Type: schema.TypeString,
Computed: true,
},
"account": {
Type: schema.TypeString,
Computed: true,
},
"version": {
Type: schema.TypeString,
Computed: true,
},
"token": {
Type: schema.TypeString,
Sensitive: true,
Computed: true,
},
},
}
}

func resourceLaceworkAgentAccessTokenCreate(d *schema.ResourceData, meta interface{}) error {
var (
lacework = meta.(*api.Client)
tokenName = d.Get("name").(string)
tokenDesc = d.Get("description").(string)
tokenEnabled = d.Get("enabled").(bool)
)

log.Printf("[INFO] Creating agent access token. name=%s, description=%s, enabled=%t",
tokenName, tokenDesc, tokenEnabled)
response, err := lacework.Agents.CreateToken(tokenName, tokenDesc)
if err != nil {
return err
}

log.Println("[INFO] Verifying server response data")
err = validateAgentTokenResponse(&response)
if err != nil {
return err
}

// @afiune at this point in time, we know the data field has a value
token := response.Data[0]
d.SetId(token.AccessToken)
d.Set("name", token.TokenAlias)
d.Set("token", token.AccessToken)
d.Set("description", token.Props.Description)
d.Set("account", token.Account)
d.Set("version", token.Version)
d.Set("enabled", token.Status())
d.Set("last_updated_time", token.LastUpdatedTime.Format(time.RFC3339))
d.Set("created_time", token.Props.CreatedTime.Format(time.RFC3339))

// very unusual but, if the user creates a token disabled, update its status
if !tokenEnabled {
log.Println("[INFO] Disabling agent access token.")
_, err = lacework.Agents.UpdateTokenStatus(token.AccessToken, false)
if err != nil {
return err
}
d.Set("enabled", false)
}

log.Printf("[INFO] Agent access token created.")
return nil
}

func resourceLaceworkAgentAccessTokenRead(d *schema.ResourceData, meta interface{}) error {
lacework := meta.(*api.Client)

log.Printf("[INFO] Reading agent access token.")
response, err := lacework.Agents.GetToken(d.Id())
if err != nil {
return err
}

for _, token := range response.Data {
if token.AccessToken == d.Id() {
d.Set("name", token.TokenAlias)
d.Set("token", token.AccessToken)
d.Set("description", token.Props.Description)
d.Set("enabled", token.Status())
d.Set("account", token.Account)
d.Set("version", token.Version)
d.Set("last_updated_time", token.LastUpdatedTime.Format(time.RFC3339))
d.Set("created_time", token.Props.CreatedTime.Format(time.RFC3339))

log.Printf("[INFO] Read agent access token. name=%s, description=%s, enabled=%t",
token.TokenAlias, token.Props.Description, token.Status())
return nil
}
}

d.SetId("")
return nil
}

func resourceLaceworkAgentAccessTokenUpdate(d *schema.ResourceData, meta interface{}) error {
var (
lacework = meta.(*api.Client)
token = api.AgentTokenRequest{
TokenAlias: d.Get("name").(string),
Enabled: 0,
Props: &api.AgentTokenProps{
Description: d.Get("description").(string),
},
}
)

if d.Get("enabled").(bool) {
token.Enabled = 1
}

log.Printf("[INFO] Updating agent access token. name=%s, description=%s, enabled=%t",
token.TokenAlias, token.Props.Description, d.Get("enabled").(bool))
response, err := lacework.Agents.UpdateToken(d.Id(), token)
if err != nil {
return err
}

log.Println("[INFO] Verifying server response data")
err = validateAgentTokenResponse(&response)
if err != nil {
return err
}

// @afiune at this point in time, we know the data field has a value
nToken := response.Data[0]
d.Set("name", nToken.TokenAlias)
d.Set("token", nToken.AccessToken)
d.Set("description", nToken.Props.Description)
d.Set("enabled", nToken.Status())
d.Set("account", nToken.Account)
d.Set("version", nToken.Version)
d.Set("last_updated_time", nToken.LastUpdatedTime.Format(time.RFC3339))
d.Set("created_time", nToken.Props.CreatedTime.Format(time.RFC3339))

log.Printf("[INFO] Agent access token updated")
return nil
}

func resourceLaceworkAgentAccessTokenDelete(d *schema.ResourceData, meta interface{}) error {
var (
lacework = meta.(*api.Client)
tokenName = fmt.Sprintf("%s-%s-deleted", d.Get("name").(string), randomString(5))
token = api.AgentTokenRequest{
TokenAlias: tokenName,
Enabled: 0,
}
)

// @afiune agent access tokens, by design, cannot be deleted, instead of deleting
// them, we only disable them, but we will also modify its TokenAlias since that
// field has a unique constraint. There can't be two tokens with the same alias.

log.Printf("[INFO] Disabling agent access token. name=%s", tokenName)
_, err := lacework.Agents.UpdateToken(d.Id(), token)
if err != nil {
return err
}

log.Printf("[INFO] Agent access token disabled and updated with name '%s'.", tokenName)
return nil
}

func importLaceworkAgentAccessToken(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
lacework := meta.(*api.Client)

log.Printf("[INFO] Importing agent access token.")
response, err := lacework.Agents.GetToken(d.Id())
if err != nil {
return nil, err
}

for _, token := range response.Data {
if token.AccessToken == d.Id() {
log.Printf("[INFO] agent access token found. name=%s, description=%s, enabled=%t",
token.TokenAlias, token.Props.Description, token.Status())
return []*schema.ResourceData{d}, nil
}
}

log.Printf("[INFO] Raw response: %v\n", response)
return nil, fmt.Errorf(
"Unable to import Lacework resource. Agent access token '%s' was not found.",
d.Id(),
)
}

// validateAgentTokenResponse checks weather or not the server response has
// any inconsistent data, it returns a friendly error message describing the
// problem and how to report it
func validateAgentTokenResponse(response *api.AgentTokensResponse) error {
if len(response.Data) == 0 {
// @afiune this edge case should never happen, if we land here it means that
// something went wrong in the server side of things (Lacework API), so let
// us inform that to our users
msg := `
Unable to read sever response data. (empty 'data' field)
This was an unexpected behavior, verify that your agent token was
created successfully and report this issue to [email protected]
`
return fmt.Errorf(msg)
}

return nil
}
15 changes: 14 additions & 1 deletion vendor/cloud.google.com/go/CHANGES.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 10 additions & 12 deletions vendor/cloud.google.com/go/go.mod

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 564c670

Please sign in to comment.