diff --git a/docs/data-sources/siteshield_map.md b/docs/data-sources/siteshield_map.md new file mode 100644 index 000000000..b6a7eb2fc --- /dev/null +++ b/docs/data-sources/siteshield_map.md @@ -0,0 +1,62 @@ +--- +layout: "akamai" +page_title: "Akamai: Site Shield" +subcategory: "Site Shield Maps" +description: |- + Site Shield +--- + +# akamai_akamai_siteshield_map + +Use the `akamai_akamai_siteshield_map` data source to retrieve information about the Site Shield maps, filtered by map ID. The information available is described +[here](https://developer.akamai.com/api/cloud_security/site_shield/v1.html#getamap). + +## Example Usage + +Basic usage: + +```hcl +provider "akamai" { + edgerc = "~/.edgerc" +} + +data "akamai_siteshield_map" "siteshield" { + map_id = 1234 +} + +output "siteshield_current_cidrs" { + value = data.akamai_siteshield_map.siteshield.current_cidrs +} + +output "siteshield_proposed_cidrs" { + value = data.akamai_siteshield_map.siteshield.proposed_cidrs +} + +output "siteshield_rule_name" { + value = data.akamai_siteshield_map.siteshield.rule_name +} + +output "siteshield_acknowledged" { + value = data.akamai_siteshield_map.siteshield.acknowledged +} + +``` + +## Argument Reference + +* `map_id` - (Required) The map ID of a specific Site Shield map to retrieve. + +The following arguments are supported: + +## Attributes Reference + +In addition to the arguments above, the following attributes are exported: + +* `current_cidrs` - A list of current CIDRs configured for the specified SS map. + +* `proposed_cidrs` - A list of proposed (new) CIDRs configured for the specified SS map. + +* `rule_name` - A map rule name available shown in properties. + +* `acknowledged` - A boolean of the aknowledgement state of the map. + diff --git a/docs/guides/akamai_provider_auth.md b/docs/guides/akamai_provider_auth.md index 896d5349a..502492009 100644 --- a/docs/guides/akamai_provider_auth.md +++ b/docs/guides/akamai_provider_auth.md @@ -43,7 +43,7 @@ supporting API service names: | **Identity and Access Management** | Identity Management: User Administration | | **Network Lists** | Network Lists | | **Property Provisioning** (Includes Common functions) | Property Manager (PAPI) | - +| **Site Shield Maps** | Site Shield Maps | -> **Note:** If you're using the Edge DNS or GTM module, you may also need the Property Manager API service. Whether you need this additional service depends on your contract and group. See [PAPI concepts](https://developer.akamai.com/api/core_features/property_manager/v1.html#papiconcepts) for more information. diff --git a/docs/guides/get_started_provider.md b/docs/guides/get_started_provider.md index 23a5563d9..173a06400 100644 --- a/docs/guides/get_started_provider.md +++ b/docs/guides/get_started_provider.md @@ -138,6 +138,8 @@ At this point in the setup, you should refer to the guides for the Akamai module | **Identity and Access Management** | [Identity and Access Management Module Guide](https://registry.terraform.io/providers/akamai/akamai/latest/docs/guides/get_started_iam) | | **Network Lists** | [Network Lists Module Guide](https://registry.terraform.io/providers/akamai/akamai/latest/docs/guides/get_started_networklists) | | **Property Provisioning** | [Property Provisioning Module Guide](https://registry.terraform.io/providers/akamai/akamai/latest/docs/guides/get_started_property) | +| **Site Shield Maps** | [Site Shield Maps Module Guide](https://registry.terraform.io/providers/akamai/akamai/latest/docs/guides/get_started_siteshield) | + -> **Note** Both Terraform and the Akamai Terraform CLI package come pre-installed in the Akamai Development Environment. See [Set Up a Development Environment](https://developer.akamai.com/blog/2020/05/26/set-development-environment) for more information. diff --git a/docs/guides/get_started_siteshield.md b/docs/guides/get_started_siteshield.md new file mode 100644 index 000000000..fbadba62e --- /dev/null +++ b/docs/guides/get_started_siteshield.md @@ -0,0 +1,71 @@ +--- +layout: "akamai" +page_title: "Module: Site Shield Maps" +description: |- + Site Shield Maps module for the Akamai Terraform Provider +--- + +# Site Shield Maps Module Guide + +The Akamai Site Shield Maps provider for Terraform gives you the ability to automate the retrieval of Site Shield maps used in various Akamai products. For customers who are already using the Akamai Network, Site Shield provides an additional layer of protection that helps prevent attackers from bypassing cloud-based protections to target the application origin. Site Shield cloaks websites and applications from the public Internet and restricts clients from directly accessing the origin. It is designed to complement the existing network infrastructure as well as advanced cloud security technologies available on the globally-distributed Akamai Intelligent Platform to mitigate the risks associated with network and application-layer threats that directly target the origin infrastructure. For more information about Site Shield Maps, see the [API documentation](https://developer.akamai.com/api/cloud_security/site_shield/v1.html) + +## Configure the Terraform Provider + +Set up your .edgerc credential files as described in [Get Started with Akamai APIs](https://developer.akamai.com/api/getting-started), and include read-write permissions for the Network Lists API. + +1. Create a new folder called `terraform` +1. Inside the new folder, create a new file called `akamai.tf`. +1. Add the provider configuration to your `akamai.tf` file: + +```hcl +provider "akamai" { + edgerc = "~/.edgerc" + config_section = "siteshield" +} +``` + +## Prerequisites + +Review [Get Started with APIs](https://learn.akamai.com/en-us/learn_akamai/getting_started_with_akamai_developers/developer_tools/getstartedapis.html) for details on how to set up client tokens to access any Akamai API. These tokens appear as custom hostnames that look like this: https://akzz-XXXXXXXXXXXXXXXX-XXXXXXXXXXXXXXXX.luna.akamaiapis.net. + +To enable this API, choose the API service named Network Lists, and set the access level to READ-WRITE. + +## Retrieving Site Shield Map Information + +You can obtain a list of all network lists available for an authenticated user belonging to a group using the [`akamai_siteshield_map`](../data-sources/siteshield_map.md) data source and its `output_text` attribute. Add the following to your `akamai.tf` file: + +```hcl +data "akamai_siteshield_map" "siteshield" { + map_id = 1234 +} + +output "siteshield_proposed_cidrs" { + value = data.akamai_siteshield_map.siteshield.proposed_cidrs +} +``` + +Once you have saved the file, switch to the terminal and initialize Terraform using the command: + +```bash +$ terraform init +``` + +This command will install the latest version of the Akamai provider, as well as any other providers necessary. To update the Akamai provider version after a new release, simply run `terraform init` again. + +## Test Your Configuration + +To test your configuration, use `terraform plan`: + +```bash +$ terraform plan +``` + +This command will make Terraform create a plan for the work it will do based on the configuration file. This will not actually make any changes and is safe to run as many times as you like. + +## Apply Changes + +To actually display the configuration information, or to create or modify resources as described further in this guide, we need to instruct Terraform to `apply` the changes outlined in the plan. To do this, in the terminal, run the command: + +```bash +$ terraform apply +``` diff --git a/pkg/providers/providers.go b/pkg/providers/providers.go index 54a1ad210..c320d36f3 100644 --- a/pkg/providers/providers.go +++ b/pkg/providers/providers.go @@ -9,4 +9,5 @@ import ( _ "github.com/akamai/terraform-provider-akamai/v2/pkg/providers/iam" _ "github.com/akamai/terraform-provider-akamai/v2/pkg/providers/networklists" _ "github.com/akamai/terraform-provider-akamai/v2/pkg/providers/property" + _ "github.com/akamai/terraform-provider-akamai/v2/pkg/providers/siteshield" ) diff --git a/pkg/providers/siteshield/data_akamai_siteshield_map.go b/pkg/providers/siteshield/data_akamai_siteshield_map.go new file mode 100644 index 000000000..3ca1cfd94 --- /dev/null +++ b/pkg/providers/siteshield/data_akamai_siteshield_map.go @@ -0,0 +1,87 @@ +package siteshield + +import ( + "context" + "errors" + "fmt" + "strconv" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v2/pkg/siteshield" + "github.com/akamai/terraform-provider-akamai/v2/pkg/akamai" + "github.com/akamai/terraform-provider-akamai/v2/pkg/tools" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceSiteShieldMap() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceSiteShieldMapRead, + Schema: map[string]*schema.Schema{ + "map_id": { + Type: schema.TypeInt, + Required: true, + }, + "current_cidrs": { + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Computed: true, + }, + "proposed_cidrs": { + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Computed: true, + }, + "rule_name": { + Type: schema.TypeString, + Computed: true, + }, + "acknowledged": { + Type: schema.TypeBool, + Computed: true, + }, + }, + } +} + +func dataSourceSiteShieldMapRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + meta := akamai.Meta(m) + client := inst.Client(meta) + logger := meta.Log("SSMAP", "dataSiteShieldMap") + + mapID, err := tools.GetIntValue("map_id", d) + d.SetId(strconv.Itoa(mapID)) + + if err != nil && !errors.Is(err, tools.ErrNotFound) { + return diag.FromErr(err) + } + + ssMapID := siteshield.SiteShieldMapRequest{UniqueID: mapID} + + ssMap, err := client.GetSiteShieldMap(ctx, ssMapID) + if err != nil { + logger.Errorf("calling 'getSiteShieldMap': %s", err.Error()) + return diag.FromErr(err) + } + + if err := d.Set("current_cidrs", ssMap.CurrentCidrs); err != nil { + logger.Errorf("error setting 'current_cidrs': %s", err.Error()) + return diag.FromErr(fmt.Errorf("%w: %s", tools.ErrValueSet, err.Error())) + } + + if err := d.Set("proposed_cidrs", ssMap.ProposedCidrs); err != nil { + logger.Errorf("error setting 'proposed_cidrs': %s", err.Error()) + return diag.FromErr(fmt.Errorf("%w: %s", tools.ErrValueSet, err.Error())) + } + + if err := d.Set("rule_name", ssMap.RuleName); err != nil { + logger.Errorf("error setting 'rule_name': %s", err.Error()) + return diag.FromErr(fmt.Errorf("%w: %s", tools.ErrValueSet, err.Error())) + } + + if err := d.Set("acknowledged", ssMap.Acknowledged); err != nil { + logger.Errorf("error setting 'acknowledged': %s", err.Error()) + return diag.FromErr(fmt.Errorf("%w: %s", tools.ErrValueSet, err.Error())) + } + + return nil +} diff --git a/pkg/providers/siteshield/data_akamai_siteshield_map_test.go b/pkg/providers/siteshield/data_akamai_siteshield_map_test.go new file mode 100644 index 000000000..f8815db39 --- /dev/null +++ b/pkg/providers/siteshield/data_akamai_siteshield_map_test.go @@ -0,0 +1,45 @@ +package siteshield + +import ( + "encoding/json" + "testing" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v2/pkg/siteshield" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/stretchr/testify/mock" +) + +func TestAccAkamaiSiteShield_data_basic(t *testing.T) { + t.Run("get SiteShield map", func(t *testing.T) { + client := &mocksiteshield{} + + cv := siteshield.SiteShieldMapResponse{} + expectJS := compactJSON(loadFixtureBytes("testdata/TestDSSiteShield/SiteShield.json")) + json.Unmarshal([]byte(expectJS), &cv) + + client.On("GetSiteShieldMap", + mock.Anything, + siteshield.SiteShieldMapRequest{UniqueID: 1234}, + ).Return(&cv, nil) + + useClient(client, func() { + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: loadFixtureString("testdata/TestDSSiteShield/get_map.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.akamai_siteshield_map.test", "map_id", "1234"), + resource.TestCheckResourceAttr("data.akamai_siteshield_map.test", "rule_name", "a;s36.akamai.net"), + resource.TestCheckResourceAttr("data.akamai_siteshield_map.test", "acknowledged", "false"), + ), + }, + }, + }) + }) + + client.AssertExpectations(t) + }) + +} diff --git a/pkg/providers/siteshield/provider.go b/pkg/providers/siteshield/provider.go new file mode 100644 index 000000000..f2776fa6d --- /dev/null +++ b/pkg/providers/siteshield/provider.go @@ -0,0 +1,120 @@ +package siteshield + +import ( + "sync" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v2/pkg/siteshield" + "github.com/akamai/terraform-provider-akamai/v2/pkg/akamai" + "github.com/akamai/terraform-provider-akamai/v2/pkg/config" + "github.com/apex/log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type ( + provider struct { + *schema.Provider + + client siteshield.SSMAPS + } + // Option is a siteshield provider option + Option func(p *provider) +) + +var ( + once sync.Once + + inst *provider +) + +// Subprovider returns a core sub provider +func Subprovider(opts ...Option) akamai.Subprovider { + once.Do(func() { + inst = &provider{Provider: Provider()} + + for _, opt := range opts { + opt(inst) + } + }) + + return inst +} + +// Provider returns the Akamai terraform.Resource provider. +func Provider() *schema.Provider { + provider := &schema.Provider{ + Schema: map[string]*schema.Schema{ + "siteshield": { + Optional: true, + Type: schema.TypeSet, + Elem: config.Options("siteshield"), + }, + }, + DataSourcesMap: map[string]*schema.Resource{ + "akamai_siteshield_map": dataSourceSiteShieldMap(), + }, + } + return provider +} + +// WithClient sets the client interface function, used for mocking and testing +func WithClient(c siteshield.SSMAPS) Option { + return func(p *provider) { + p.client = c + } +} + +// Client returns the PAPI interface +func (p *provider) Client(meta akamai.OperationMeta) siteshield.SSMAPS { + if p.client != nil { + return p.client + } + return siteshield.Client(meta.Session()) +} + +func getSiteShieldV1Service(d *schema.ResourceData) error { + var section string + + if section != "" { + if err := d.Set("config_section", section); err != nil { + return err + } + } + + return nil +} + +func (p *provider) Name() string { + return "siteshield" +} + +// SiteShieldProviderVersion update version string anytime provider adds new features +const SiteShieldProviderVersion string = "v0.0.1" + +func (p *provider) Version() string { + return SiteShieldProviderVersion +} + +func (p *provider) Schema() map[string]*schema.Schema { + return p.Provider.Schema +} + +func (p *provider) Resources() map[string]*schema.Resource { + return p.Provider.ResourcesMap +} + +func (p *provider) DataSources() map[string]*schema.Resource { + return p.Provider.DataSourcesMap +} + +func (p *provider) Configure(log log.Interface, d *schema.ResourceData) diag.Diagnostics { + log.Debug("START Configure") + + err := getSiteShieldV1Service(d) + if err != nil { + return diag.FromErr(err) + } + + return nil +} diff --git a/pkg/providers/siteshield/provider_test.go b/pkg/providers/siteshield/provider_test.go new file mode 100644 index 000000000..faddca313 --- /dev/null +++ b/pkg/providers/siteshield/provider_test.go @@ -0,0 +1,112 @@ +package siteshield + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "strings" + "sync" + "testing" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v2/pkg/siteshield" + "github.com/akamai/terraform-provider-akamai/v2/pkg/akamai" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var testAccProviders map[string]*schema.Provider +var testProvider *schema.Provider + +func init() { + testProvider = akamai.Provider(Subprovider())() + testAccProviders = map[string]*schema.Provider{ + "akamai": testProvider, + } +} + +func TestProvider(t *testing.T) { + if err := Provider().InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } +} + +func testAccPreCheck(t *testing.T) { + +} + +func getTestProvider() *schema.Provider { + return testProvider +} + +// Only allow one test at a time to patch the client via useClient() +var clientLock sync.Mutex + +// useClient swaps out the client on the global instance for the duration of the given func +func useClient(client siteshield.SSMAPS, f func()) { + clientLock.Lock() + orig := inst.client + inst.client = client + + defer func() { + inst.client = orig + clientLock.Unlock() + }() + + f() +} + +// TODO marks a test as being in a "pending" state and logs a message telling the user why. Such tests are expected to +// fail for the time being and may exist for the sake of unfinished/future features or to document known buggy cases +// that won't be fixed right away. The failure of a pending test is not considered an error and the test will therefore +// be skipped unless the TEST_TODO environment variable is set to a non-empty value. +func TODO(t *testing.T, message string) { + t.Helper() + t.Log(fmt.Sprintf("TODO: %s", message)) + + if os.Getenv("TEST_TODO") == "" { + t.Skip("TODO: Set TEST_TODO=1 in env to run this test") + } +} + +func setEnv(home string, env map[string]string) { + os.Clearenv() + os.Setenv("HOME", home) + if len(env) > 0 { + for key, val := range env { + os.Setenv(key, val) + } + } +} + +func restoreEnv(env []string) { + os.Clearenv() + for _, value := range env { + envVar := strings.Split(value, "=") + os.Setenv(envVar[0], envVar[1]) + } +} + +// loadFixtureBytes returns the entire contents of the given file as a byte slice +func loadFixtureBytes(path string) []byte { + contents, err := ioutil.ReadFile(path) + if err != nil { + panic(err) + } + return contents +} + +// loadFixtureString returns the entire contents of the given file as a string +func loadFixtureString(path string) string { + return string(loadFixtureBytes(path)) +} + +// compactJSON converts a JSON-encoded byte slice to a compact form (so our JSON fixtures can be readable) +func compactJSON(encoded []byte) string { + buf := bytes.Buffer{} + if err := json.Compact(&buf, encoded); err != nil { + panic(fmt.Sprintf("%s: %s", err, string(encoded))) + } + + return buf.String() +} diff --git a/pkg/providers/siteshield/siteshield.go b/pkg/providers/siteshield/siteshield.go new file mode 100644 index 000000000..42370a101 --- /dev/null +++ b/pkg/providers/siteshield/siteshield.go @@ -0,0 +1,9 @@ +// +build all siteshield + +package siteshield + +import "github.com/akamai/terraform-provider-akamai/v2/pkg/providers/registry" + +func init() { + registry.RegisterProvider(Subprovider()) +} diff --git a/pkg/providers/siteshield/siteshield_test.go b/pkg/providers/siteshield/siteshield_test.go new file mode 100644 index 000000000..acfaf26f0 --- /dev/null +++ b/pkg/providers/siteshield/siteshield_test.go @@ -0,0 +1,42 @@ +package siteshield + +import ( + "context" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v2/pkg/siteshield" + "github.com/stretchr/testify/mock" +) + +type mocksiteshield struct { + mock.Mock +} + +func (p *mocksiteshield) GetSiteShieldMaps(ctx context.Context) (*siteshield.GetSiteShieldMapsResponse, error) { + args := p.Called(ctx) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*siteshield.GetSiteShieldMapsResponse), args.Error(1) +} + +func (p *mocksiteshield) GetSiteShieldMap(ctx context.Context, params siteshield.SiteShieldMapRequest) (*siteshield.SiteShieldMapResponse, error) { + args := p.Called(ctx, params) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*siteshield.SiteShieldMapResponse), args.Error(1) +} + +func (p *mocksiteshield) AckSiteShieldMap(ctx context.Context, params siteshield.SiteShieldMapRequest) (*siteshield.SiteShieldMapResponse, error) { + args := p.Called(ctx, params) + + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*siteshield.SiteShieldMapResponse), args.Error(1) +} diff --git a/pkg/providers/siteshield/testdata/TestDSSiteShield/SiteShield.json b/pkg/providers/siteshield/testdata/TestDSSiteShield/SiteShield.json new file mode 100644 index 000000000..f3fa7b20f --- /dev/null +++ b/pkg/providers/siteshield/testdata/TestDSSiteShield/SiteShield.json @@ -0,0 +1,21 @@ +{ + "acknowledged": false, + "contacts": [ + "test@akamai.com", + "test2@akamai.com" + ], + "currentCidrs": [ + "131.103.136.0/24", "131.103.137.0/24", + "165.254.127.0/24", "165.254.137.0/24", "184.25.254.0/24" + ], + "proposedCidrs": [ + "107.14.42.0/24", "117.103.188.0/24", "195.59.54.0/24", + "209.211.216.0/24", "216.246.75.0/24" + ], + "ruleName": "a;s36.akamai.net", + "type": "Production", + "service": "S", + "shared": false, + "acknowledgeRequiredBy": 1392154239000, + "previouslyAcknowledgedOn": 1392154239000 +} \ No newline at end of file diff --git a/pkg/providers/siteshield/testdata/TestDSSiteShield/get_map.tf b/pkg/providers/siteshield/testdata/TestDSSiteShield/get_map.tf new file mode 100644 index 000000000..e0652c9ae --- /dev/null +++ b/pkg/providers/siteshield/testdata/TestDSSiteShield/get_map.tf @@ -0,0 +1,8 @@ +provider "akamai" { + edgerc = "~/.edgerc" +} + + +data "akamai_siteshield_map" "test" { + map_id = 1234 +} \ No newline at end of file diff --git a/pkg/providers/siteshield/testdata/TestDSSiteShield/versions.tf b/pkg/providers/siteshield/testdata/TestDSSiteShield/versions.tf new file mode 100644 index 000000000..38b54ec2f --- /dev/null +++ b/pkg/providers/siteshield/testdata/TestDSSiteShield/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + akamai = { + source = "akamai/akamai" + } + } + required_version = ">= 0.13" +}