Skip to content

Commit 60c8b1e

Browse files
authored
Merge pull request #323 from aquasecurity/0.8.41
feat: Support Authentication using API Keys
2 parents 22c6ec6 + 281fb06 commit 60c8b1e

74 files changed

Lines changed: 1241 additions & 105 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

DEVELOPMENT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ git clone https://github.com/aquasecurity/terraform-provider-aquasec.git
3232
3333
cd terraform-provider-aquasec
3434
35-
git checkout v0.8.40
35+
git checkout v0.8.41
3636
```
3737

3838
**Build and install the provider**

GNUmakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ HOSTNAME := github.com
66
NAMESPACE := aquasec
77
NAME := aquasec
88
BINARY := terraform-provider-${NAME}
9-
VERSION := 0.8.40
9+
VERSION := 0.8.41
1010
OS_ARCH := $(shell go env GOOS)_$(shell go env GOARCH)
1111

1212
default: build

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ To quickly get started using the Aquasec provider for Terraform, configure the p
4343
terraform {
4444
required_providers {
4545
aquasec = {
46-
version = "0.8.40"
46+
version = "0.8.41"
4747
source = "aquasecurity/aquasec"
4848
}
4949
}

aquasec/data_aqua_api_key.go

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
package aquasec
2+
3+
import (
4+
"context"
5+
"log"
6+
"strconv"
7+
"time"
8+
9+
"github.com/aquasecurity/terraform-provider-aquasec/client"
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
12+
)
13+
14+
func dataSourceAPIKeys() *schema.Resource {
15+
return &schema.Resource{
16+
Description: "Data source `aquasec_aqua_api_keys` provides all API keys by limit and offset.",
17+
ReadContext: dataAPIKeyRead,
18+
Schema: map[string]*schema.Schema{
19+
"id": {
20+
Type: schema.TypeString,
21+
Optional: true,
22+
ExactlyOneOf: []string{"id", "limit"},
23+
},
24+
"limit": {
25+
Type: schema.TypeInt,
26+
Optional: true,
27+
ExactlyOneOf: []string{"id", "limit"},
28+
},
29+
"offset": {
30+
Type: schema.TypeInt,
31+
Optional: true,
32+
},
33+
"apikeys": {
34+
Type: schema.TypeList,
35+
Computed: true,
36+
Elem: &schema.Resource{
37+
Schema: map[string]*schema.Schema{
38+
"id": {
39+
Type: schema.TypeInt,
40+
Computed: true,
41+
},
42+
"access_key": {
43+
Type: schema.TypeString,
44+
Computed: true,
45+
},
46+
"description": {
47+
Type: schema.TypeString,
48+
Computed: true,
49+
},
50+
"created": {
51+
Type: schema.TypeString,
52+
Computed: true,
53+
},
54+
"open_access": {
55+
Type: schema.TypeBool,
56+
Computed: true,
57+
},
58+
"scans_per_month": {
59+
Type: schema.TypeInt,
60+
Computed: true,
61+
},
62+
"account_id": {
63+
Type: schema.TypeInt,
64+
Computed: true,
65+
},
66+
"enabled": {
67+
Type: schema.TypeBool,
68+
Computed: true,
69+
},
70+
"roles": {
71+
Type: schema.TypeList,
72+
Description: "The roles that will be assigned to the API key.",
73+
Computed: true,
74+
Elem: &schema.Schema{
75+
Type: schema.TypeString,
76+
},
77+
},
78+
"group_id": {
79+
Type: schema.TypeInt,
80+
Description: "The group ID that is associated with the API key.",
81+
Computed: true,
82+
},
83+
"expiration": {
84+
Type: schema.TypeInt,
85+
Description: "The date of the API key's expiry",
86+
Computed: true,
87+
},
88+
"permission_ids": {
89+
Type: schema.TypeList,
90+
Description: "List of permission IDs for the API key, if empty the API" +
91+
"key has global admin permissions.",
92+
Computed: true,
93+
Elem: &schema.Schema{
94+
Type: schema.TypeInt,
95+
},
96+
},
97+
"ip_addresses": {
98+
Type: schema.TypeList,
99+
Description: "List of IP addresses the API key can be used from.",
100+
Computed: true,
101+
Elem: &schema.Schema{
102+
Type: schema.TypeString,
103+
},
104+
},
105+
},
106+
},
107+
},
108+
},
109+
}
110+
}
111+
112+
func dataAPIKeyRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
113+
c := m.(*client.Client)
114+
var diags diag.Diagnostics
115+
116+
if idRaw, ok := d.GetOk("id"); ok {
117+
id, err := strconv.Atoi(idRaw.(string))
118+
if err != nil {
119+
return diag.FromErr(err)
120+
}
121+
key, err := c.GetApiKey(id)
122+
if err != nil {
123+
return diag.FromErr(err)
124+
}
125+
126+
if key.ID == 0 {
127+
return diag.Errorf("API key returned with empty or zero id")
128+
}
129+
130+
created := ""
131+
if key.CreatedAt != "" {
132+
if t, err := parseWithFallback(key.CreatedAt, time.RFC3339, time.Time{}); err == nil {
133+
created = t.Format(time.RFC3339)
134+
} else {
135+
log.Printf("[WARN] could not parse created %q: %v", key.CreatedAt, err)
136+
}
137+
}
138+
139+
item := map[string]interface{}{
140+
"id": key.ID,
141+
"access_key": key.AccessKey,
142+
"description": key.Description,
143+
"created": created,
144+
"open_access": key.OpenAccess,
145+
"scans_per_month": key.ScansPerMonth,
146+
"account_id": key.AccountID,
147+
"enabled": key.Enabled,
148+
"roles": key.Roles,
149+
"permission_ids": key.PermissionIDs,
150+
"expiration": key.Expiration,
151+
"group_id": key.GroupID,
152+
"ip_addresses": key.IPAddresses,
153+
}
154+
if err := d.Set("apikeys", []interface{}{item}); err != nil {
155+
return diag.FromErr(err)
156+
}
157+
d.SetId(strconv.Itoa(id))
158+
return diags
159+
}
160+
if limitRaw, ok := d.GetOk("limit"); ok {
161+
limit := limitRaw.(int)
162+
offset := d.Get("offset").(int)
163+
164+
if limit == 0 {
165+
return diag.Errorf("`limit` must be greater than 0 when using limit/offset")
166+
}
167+
168+
keys, err := c.GetApiKeys(limit, offset)
169+
if err != nil {
170+
return diag.FromErr(err)
171+
}
172+
173+
var items []map[string]interface{}
174+
for _, k := range keys {
175+
if k.ID == 0 {
176+
log.Printf("[WARN] skipping API key with empty id")
177+
continue
178+
}
179+
180+
created := ""
181+
if k.CreatedAt != "" {
182+
if t, err := parseWithFallback(k.CreatedAt, time.RFC3339, time.Time{}); err == nil {
183+
created = t.Format(time.RFC3339)
184+
} else {
185+
log.Printf("[WARN] could not parse created %q: %v", k.CreatedAt, err)
186+
}
187+
}
188+
189+
items = append(items, map[string]interface{}{
190+
"id": k.ID,
191+
"access_key": k.AccessKey,
192+
"description": k.Description,
193+
"created": created,
194+
"open_access": k.OpenAccess,
195+
"scans_per_month": k.ScansPerMonth,
196+
"account_id": k.AccountID,
197+
"enabled": k.Enabled,
198+
"roles": k.Roles,
199+
"permission_ids": k.PermissionIDs,
200+
"expiration": k.Expiration,
201+
"group_id": k.GroupID,
202+
"ip_addresses": k.IPAddresses,
203+
})
204+
}
205+
206+
if err := d.Set("apikeys", items); err != nil {
207+
return diag.FromErr(err)
208+
}
209+
d.SetId(strconv.FormatInt(time.Now().Unix(), 10))
210+
return diags
211+
}
212+
213+
return diags
214+
}
215+
216+
func parseWithFallback(s, layout string, fallback time.Time) (time.Time, error) {
217+
t, err := time.Parse(layout, s)
218+
if err != nil {
219+
return fallback, err
220+
}
221+
return t, nil
222+
}

aquasec/data_aqua_api_key_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package aquasec
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
"testing"
7+
8+
"github.com/aquasecurity/terraform-provider-aquasec/client"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
11+
)
12+
13+
func TestAccAquaSecAPIKeysDataSource_byID(t *testing.T) {
14+
t.Parallel()
15+
resource.Test(t, resource.TestCase{
16+
PreCheck: func() { testAccPreCheck(t) },
17+
Providers: testAccProviders,
18+
CheckDestroy: testAccCheckAquaSecAPIKeyDestroy,
19+
Steps: []resource.TestStep{
20+
{
21+
Config: testAccAquaSecAPIKeyAndDataSourceByID(),
22+
Check: resource.ComposeTestCheckFunc(
23+
testAccCheckAquaSecAPIKeyCreated("aquasec_aqua_api_key.test"),
24+
testAccCheckAquaSecAPIKeyDataByID("data.aquasec_aqua_api_keys.apikeys"),
25+
),
26+
},
27+
},
28+
})
29+
}
30+
31+
func testAccAquaSecAPIKeyAndDataSourceByID() string {
32+
return `
33+
resource "aquasec_aqua_api_key" "test" {
34+
description = "Terraform test key"
35+
enabled = true
36+
roles = ["role1"]
37+
ip_addresses = ["127.0.0.1"]
38+
expiration = 30
39+
}
40+
data "aquasec_aqua_api_keys" "apikeys" {
41+
id = aquasec_aqua_api_key.test.id
42+
}
43+
44+
output "first_desc" {
45+
value = data.aquasec_aqua_api_keys.apikeys.apikeys[0].description
46+
}
47+
output "test_api_key_id" {
48+
value = data.aquasec_aqua_api_keys.apikeys.apikeys[0].id
49+
}
50+
`
51+
}
52+
53+
func testAccCheckAquaSecAPIKeyCreated(n string) resource.TestCheckFunc {
54+
return func(s *terraform.State) error {
55+
rs, ok := s.RootModule().Resources[n]
56+
if !ok {
57+
return fmt.Errorf("resource %q not found in state", n)
58+
}
59+
if rs.Primary.ID == "" {
60+
return fmt.Errorf("API Key wasn’t created (empty ID)")
61+
}
62+
return nil
63+
}
64+
}
65+
66+
func testAccCheckAquaSecAPIKeyDataByID(n string) resource.TestCheckFunc {
67+
return func(s *terraform.State) error {
68+
rs, ok := s.RootModule().Resources[n]
69+
if !ok {
70+
return fmt.Errorf("data source %q not found in state", n)
71+
}
72+
if rs.Primary.ID == "" {
73+
return fmt.Errorf("data source %q has no ID", n)
74+
}
75+
76+
descKey := "apikeys.0.description"
77+
if v, ok := rs.Primary.Attributes[descKey]; !ok || v == "" {
78+
return fmt.Errorf("attribute %q is empty or not found", descKey)
79+
}
80+
return nil
81+
}
82+
}
83+
84+
func testAccCheckAquaSecAPIKeyDestroy(s *terraform.State) error {
85+
client := testAccProvider.Meta().(*client.Client)
86+
87+
for _, rs := range s.RootModule().Resources {
88+
if rs.Type != "aquasec_aqua_api_key" {
89+
continue
90+
}
91+
92+
id := rs.Primary.ID
93+
if id == "" {
94+
continue
95+
}
96+
97+
// Try fetching the deleted key, expect failure
98+
keyID, err := strconv.Atoi(id)
99+
if err != nil {
100+
return fmt.Errorf("invalid ID %q: %v", id, err)
101+
}
102+
103+
_, err = client.GetApiKey(keyID)
104+
if err == nil {
105+
return fmt.Errorf("API Key still exists (ID %d)", keyID)
106+
}
107+
}
108+
109+
return nil
110+
}

aquasec/provider.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ func Provider(v string) *schema.Provider {
101101
"aquasec_role_mapping_saas": resourceRoleMappingSaas(),
102102
"aquasec_permission_set_saas": resourcePermissionSetSaas(),
103103
"aquasec_assurance_custom_script": resourceAssuranceScript(),
104+
"aquasec_aqua_api_key": resourceAPIKey(),
104105
},
105106
DataSourcesMap: map[string]*schema.Resource{
106107
"aquasec_users": dataSourceUsers(),
@@ -133,6 +134,7 @@ func Provider(v string) *schema.Provider {
133134
"aquasec_roles_mapping_saas": dataSourceRolesMappingSaas(),
134135
"aquasec_permissions_sets_saas": dataSourcePermissionsSetsSaas(),
135136
"aquasec_assurance_custom_script": dataSourceAssuranceScript(),
137+
"aquasec_aqua_api_keys": dataSourceAPIKeys(),
136138
},
137139
ConfigureContextFunc: providerConfigure,
138140
}

0 commit comments

Comments
 (0)