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

f-aws_directconnect_connection_macsec #26274

Merged
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
8d71007
First push to add MACSec support
Jun 21, 2022
5c4d093
Add working acceptance tests
ddericco Aug 10, 2022
6738f1f
Add changelog, run acc test linting
ddericco Aug 12, 2022
a1c5b07
[WIP] Add and update docs
ddericco Aug 23, 2022
45071a7
Minor doc/linting fixes
ddericco Aug 25, 2022
dd223d2
Add support to delete keys on disassociate
ddericco Aug 25, 2022
4ca85df
Add read function to macsec_key resource
ddericco Sep 1, 2022
96a5dd5
Update acceptance tests, clean up dx_connection resource
ddericco Sep 2, 2022
c23c8f1
Minor linting fixes
ddericco Sep 2, 2022
063abff
Add sweeper for aws_dx_macsec_keys, remove secret delete function
ddericco Sep 15, 2022
810926c
Update aws_dx_connection documentation, fix sweeper attribute error
ddericco Sep 15, 2022
ae5cc7f
Remove unneeded schema items, fix docs conflict
ddericco Sep 15, 2022
c12eecc
Minor linting fixes
ddericco Sep 15, 2022
143007a
Additional linting fixes
ddericco Sep 15, 2022
b07f472
Apply code review suggestion: `request_macsec` attribute
ddericco Dec 15, 2022
7f5f063
Merge branch 'hashicorp:main' into f-aws_directconnect_connection-macsec
ddericco Dec 15, 2022
a72afad
Add ForceNew to required attribute, remove update Noop
ddericco Dec 15, 2022
6f984de
Add ForceNew to macsec connection, remove check on macsec_capable attr
ddericco Dec 15, 2022
db13c88
Rerun Terrafmt linting
ddericco Dec 15, 2022
047756f
Apply CR suggestion: rename to `aws_dx_macsec_key_association`
ddericco Dec 15, 2022
8ad97cb
Update internal/provider/provider.go
ddericco Dec 15, 2022
fd0aa9d
Rename docs, add encryption_mode acc test [WIP]
ddericco Dec 15, 2022
3e236d9
Add skip_destroy argument for aws_dx_connection
ddericco Dec 16, 2022
4f71c06
WIP - encryption_mode acceptance test
ddericco Dec 16, 2022
63282e5
Tweak CHANGELOG entries.
ewbankkit Dec 16, 2022
9da4e2b
Fix semgrep 'dgryski.semgrep-go.oddifsequence.odd-sequence-ifs'.
ewbankkit Dec 16, 2022
3d6318c
Fix golangci-lint 'whitespace'.
ewbankkit Dec 16, 2022
e87145e
Remove unused commented functions
ddericco Dec 16, 2022
3f6e762
Cosmetics.
ewbankkit Dec 16, 2022
9e087b5
Finalize encryption_mode acc test
ddericco Dec 18, 2022
1a820f4
r/aws_dx_connection: Fix golangci-lint 'unparam'.
ewbankkit Dec 19, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changelog/26274.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:new-resource
aws_dx_macsec_key
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered naming this resource aws_dx_macsec_key_association? Since MACsec keys can only be associated/described/dissociated, renaming the resource may help preventing any confusion.

Suggested change
aws_dx_macsec_key
aws_dx_macsec_key_association

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree that the resource should be named aws_dx_connection_macsec_key_association.

```

```release-note:enhancement
resource/aws_dx_connection: Add arguments to enable MACsec support
```
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -1265,6 +1265,7 @@ func New(_ context.Context) (*schema.Provider, error) {
"aws_dx_hosted_transit_virtual_interface": directconnect.ResourceHostedTransitVirtualInterface(),
"aws_dx_hosted_transit_virtual_interface_accepter": directconnect.ResourceHostedTransitVirtualInterfaceAccepter(),
"aws_dx_lag": directconnect.ResourceLag(),
"aws_dx_macsec_key": directconnect.ResourceMacSecKey(),
ddericco marked this conversation as resolved.
Show resolved Hide resolved
"aws_dx_private_virtual_interface": directconnect.ResourcePrivateVirtualInterface(),
"aws_dx_public_virtual_interface": directconnect.ResourcePublicVirtualInterface(),
"aws_dx_transit_virtual_interface": directconnect.ResourceTransitVirtualInterface(),
Expand Down
43 changes: 43 additions & 0 deletions internal/service/directconnect/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/aws/aws-sdk-go/service/directconnect"
"github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
tftags "github.com/hashicorp/terraform-provider-aws/internal/tags"
"github.com/hashicorp/terraform-provider-aws/internal/tfresource"
Expand Down Expand Up @@ -40,6 +41,13 @@ func ResourceConnection() *schema.Resource {
ForceNew: true,
ValidateFunc: validConnectionBandWidth(),
},
// The MAC Security (MACsec) connection encryption mode.
"encryption_mode": {
Type: schema.TypeString,
Computed: true,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{"no_encrypt", "should_encrypt", "must_encrypt"}, false),
ewbankkit marked this conversation as resolved.
Show resolved Hide resolved
},
"has_logical_redundancy": {
Type: schema.TypeString,
Computed: true,
Expand All @@ -53,6 +61,18 @@ func ResourceConnection() *schema.Resource {
Required: true,
ForceNew: true,
},
// Indicates whether the connection supports MAC Security (MACsec).
"macsec_capable": {
Type: schema.TypeBool,
Computed: true,
},
// Enable or disable MAC Security (MACsec) on this connection.
"macsec_requested": {
ddericco marked this conversation as resolved.
Show resolved Hide resolved
Type: schema.TypeBool,
Optional: true,
Default: false,
ForceNew: true,
},
"name": {
Type: schema.TypeString,
Required: true,
Expand All @@ -62,6 +82,11 @@ func ResourceConnection() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},
// The MAC Security (MACsec) port link status of the connection.
"port_encryption_status": {
Type: schema.TypeString,
Computed: true,
},
"provider_name": {
Type: schema.TypeString,
Optional: true,
Expand All @@ -86,6 +111,7 @@ func resourceConnectionCreate(d *schema.ResourceData, meta interface{}) error {
Bandwidth: aws.String(d.Get("bandwidth").(string)),
ConnectionName: aws.String(name),
Location: aws.String(d.Get("location").(string)),
RequestMACSec: aws.Bool(d.Get("macsec_requested").(bool)),
ddericco marked this conversation as resolved.
Show resolved Hide resolved
}

if v, ok := d.GetOk("provider_name"); ok {
Expand All @@ -104,6 +130,7 @@ func resourceConnectionCreate(d *schema.ResourceData, meta interface{}) error {
}

d.SetId(aws.StringValue(output.ConnectionId))
d.Set("macsec_requested", d.Get("macsec_requested").(bool))
ddericco marked this conversation as resolved.
Show resolved Hide resolved

return resourceConnectionRead(d, meta)
}
Expand Down Expand Up @@ -135,11 +162,14 @@ func resourceConnectionRead(d *schema.ResourceData, meta interface{}) error {
d.Set("arn", arn)
d.Set("aws_device", connection.AwsDeviceV2)
d.Set("bandwidth", connection.Bandwidth)
d.Set("encryption_mode", connection.EncryptionMode)
d.Set("has_logical_redundancy", connection.HasLogicalRedundancy)
d.Set("jumbo_frame_capable", connection.JumboFrameCapable)
d.Set("location", connection.Location)
d.Set("macsec_capable", connection.MacSecCapable)
d.Set("name", connection.ConnectionName)
d.Set("owner_account_id", connection.OwnerAccount)
d.Set("port_encryption_status", connection.PortEncryptionStatus)
d.Set("provider_name", connection.ProviderName)

tags, err := ListTags(conn, arn)
Expand All @@ -165,6 +195,19 @@ func resourceConnectionRead(d *schema.ResourceData, meta interface{}) error {
func resourceConnectionUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).DirectConnectConn

// Update encryption mode
if d.HasChange("encryption_mode") {
input := &directconnect.UpdateConnectionInput{
ConnectionId: aws.String(d.Id()),
EncryptionMode: aws.String(d.Get("encryption_mode").(string)),
}
log.Printf("[DEBUG] Modifying Direct Connect connection attributes: %s", input)
_, err := conn.UpdateConnection(input)
if err != nil {
return fmt.Errorf("error modifying Direct Connect connection (%s) attributes: %s", d.Id(), err)
}
}

arn := d.Get("arn").(string)
if d.HasChange("tags_all") {
o, n := d.GetChange("tags_all")
Expand Down
62 changes: 62 additions & 0 deletions internal/service/directconnect/connection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,44 @@ func TestAccDirectConnectConnection_disappears(t *testing.T) {
})
}

ewbankkit marked this conversation as resolved.
Show resolved Hide resolved
func TestAccDirectConnectConnection_macsecRequested(t *testing.T) {
var connection directconnect.Connection
resourceName := "aws_dx_connection.test"
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ErrorCheck: acctest.ErrorCheck(t, directconnect.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckConnectionDestroy,
Steps: []resource.TestStep{
{
Config: testAccConnectionConfig_macsecEnabled(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckConnectionExists(resourceName, &connection),
acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "directconnect", regexp.MustCompile(`dxcon/.+`)),
resource.TestCheckResourceAttr(resourceName, "bandwidth", "100Gbps"),
resource.TestCheckResourceAttrSet(resourceName, "location"),
// macsec_capable will not return "true" while connection is in "Requesting" state
resource.TestCheckResourceAttr(resourceName, "macsec_capable", "false"),
ddericco marked this conversation as resolved.
Show resolved Hide resolved
resource.TestCheckResourceAttr(resourceName, "macsec_requested", "true"),
ddericco marked this conversation as resolved.
Show resolved Hide resolved
acctest.CheckResourceAttrAccountID(resourceName, "owner_account_id"),
resource.TestCheckResourceAttr(resourceName, "name", rName),
resource.TestCheckResourceAttrSet(resourceName, "provider_name"),
resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
// Ignore the "macsec_requested" attribute as isn't returned by the API during read/refresh
ImportStateVerifyIgnore: []string{"macsec_requested"},
},
},
})
}

func TestAccDirectConnectConnection_providerName(t *testing.T) {
var connection directconnect.Connection
resourceName := "aws_dx_connection.test"
Expand Down Expand Up @@ -221,6 +259,30 @@ resource "aws_dx_connection" "test" {
`, rName)
}

func testAccConnectionConfig_macsecEnabled(rName string) string {
return fmt.Sprintf(`
data "aws_dx_locations" "test" {}

locals {
location_codes = tolist(data.aws_dx_locations.test.location_codes)
idx = min(2, length(local.location_codes) - 1)
}

data "aws_dx_location" "test" {
location_code = local.location_codes[local.idx]
}

resource "aws_dx_connection" "test" {
name = %[1]q
bandwidth = "100Gbps"
location = data.aws_dx_location.test.location_code
macsec_requested = true
ddericco marked this conversation as resolved.
Show resolved Hide resolved

provider_name = data.aws_dx_location.test.available_providers[0]
}
`, rName)
}

func testAccConnectionConfig_providerName(rName string) string {
return fmt.Sprintf(`
data "aws_dx_locations" "test" {}
Expand Down
173 changes: 173 additions & 0 deletions internal/service/directconnect/macsec_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package directconnect

import (
"fmt"
"log"
"regexp"
"strings"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/directconnect"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
)

func ResourceMacSecKey() *schema.Resource {
ddericco marked this conversation as resolved.
Show resolved Hide resolved
return &schema.Resource{
// MacSecKey resource only supports create (Associate), read (Describe) and delete (Disassociate)
Create: resourceMacSecKeyCreate,
Read: resourceMacSecKeyRead,
// You cannot modify a MACsec secret key after you associate it with a connection.
// To modify the key, disassociate the key from the connection, and then associate
// a new key with the connection
Update: schema.Noop,
ddericco marked this conversation as resolved.
Show resolved Hide resolved
Delete: resourceMacSecKeyDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"cak": {
Type: schema.TypeString,
Optional: true,
// CAK requires CKN
RequiredWith: []string{"ckn"},
ValidateFunc: validation.StringMatch(regexp.MustCompile(`[a-fA-F0-9]{64}$`), "Must be 64-character hex code string"),
},
"ckn": {
Type: schema.TypeString,
Computed: true,
ewbankkit marked this conversation as resolved.
Show resolved Hide resolved
Optional: true,
AtLeastOneOf: []string{"ckn", "secret_arn"},
ValidateFunc: validation.StringMatch(regexp.MustCompile(`[a-fA-F0-9]{64}$`), "Must be 64-character hex code string"),
},
"connection_id": {
Type: schema.TypeString,
Required: true,
},
"secret_arn": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ewbankkit marked this conversation as resolved.
Show resolved Hide resolved
AtLeastOneOf: []string{"ckn", "secret_arn"},
},
"start_on": {
Type: schema.TypeString,
Computed: true,
},
"state": {
Type: schema.TypeString,
Computed: true,
},
},
}
}

func resourceMacSecKeyCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).DirectConnectConn

input := &directconnect.AssociateMacSecKeyInput{
ConnectionId: aws.String(d.Get("connection_id").(string)),
}

if d.Get("ckn").(string) != "" {
input.Cak = aws.String(d.Get("cak").(string))
input.Ckn = aws.String(d.Get("ckn").(string))
}

if d.Get("secret_arn").(string) != "" {
input.SecretARN = aws.String(d.Get("secret_arn").(string))
}

log.Printf("[DEBUG] Creating MACSec secret key on Direct Connect Connection: %s", *input.ConnectionId)
output, err := conn.AssociateMacSecKey(input)

if err != nil {
return fmt.Errorf("error creating MACSec secret key on Direct Connect Connection (%s): %w", *input.ConnectionId, err)
}

secret_arn := MacSecKeyParseSecretARN(output)

// Create a composite ID based on connection ID and secret ARN
d.SetId(fmt.Sprintf("%s_%s", secret_arn, aws.StringValue(output.ConnectionId)))

d.Set("secret_arn", secret_arn)

return resourceMacSecKeyRead(d, meta)
}

func resourceMacSecKeyRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).DirectConnectConn

secretArn, connId, err := MacSecKeyParseID(d.Id())
if err != nil {
return fmt.Errorf("unexpected format of ID (%s), expected secretArn_connectionId", d.Id())
}

connection, err := FindConnectionByID(conn, connId)
if err != nil {
return fmt.Errorf("error reading Direct Connect Connection (%s): %w", d.Id(), err)
}

if connection.MacSecKeys == nil {
return fmt.Errorf("no MACSec keys found on Direct Connect Connection (%s)", d.Id())
}

for _, key := range connection.MacSecKeys {
if aws.StringValue(key.SecretARN) == aws.StringValue(&secretArn) {
d.Set("ckn", key.Ckn)
d.Set("connection_id", connId)
d.Set("secret_arn", key.SecretARN)
d.Set("start_on", key.StartOn)
d.Set("state", key.State)
}
}

return nil
}

func resourceMacSecKeyDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).DirectConnectConn

input := &directconnect.DisassociateMacSecKeyInput{
ConnectionId: aws.String(d.Get("connection_id").(string)),
SecretARN: aws.String(d.Get("secret_arn").(string)),
}

log.Printf("[DEBUG] Disassociating MACSec secret key on Direct Connect Connection: %s", *input.ConnectionId)
_, err := conn.DisassociateMacSecKey(input)

if err != nil {
return fmt.Errorf("Unable to disassociate MACSec secret key on Direct Connect Connection (%s): %w", *input.ConnectionId, err)
}

return nil
}

// MacSecKeyParseSecretARN parses the secret ARN returned from a CMK or secret_arn
func MacSecKeyParseSecretARN(output *directconnect.AssociateMacSecKeyOutput) string {
var result string

for _, key := range output.MacSecKeys {
if key == nil {
continue
}
if key != nil {
result = aws.StringValue(key.SecretARN)
}
}
return result
}

// MacSecKeyParseID parses the resource ID and returns the secret ARN and connection ID
func MacSecKeyParseID(id string) (string, string, error) {
parts := strings.SplitN(id, "_", 2)

if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return "", "", &resource.NotFoundError{}
}

return parts[0], parts[1], nil
}
Loading