diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..09177d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +**/*.terraform* +**/*.tfstate* +**/*.hcl +vendor/ +bin/ diff --git a/Makefile b/Makefile index d5b853d..9e4af64 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ HOSTNAME=registry.terraform.io NAMESPACE=on2itsecurity NAME=auxo BINARY=terraform-provider-${NAME} -VERSION=0.0.5 +VERSION=0.0.6 OS_ARCH=darwin_arm64 #amd64 default: build diff --git a/README.md b/README.md index 417fc77..714acea 100644 --- a/README.md +++ b/README.md @@ -102,3 +102,11 @@ scp ./terraform-provider-auxo @:~/ mkdir -p ~/.terraform.d/plugins/on2itsecurity/auxo/0.1/linux_amd64 mv ~/terraform-provider-auxo ~/.terraform.d/plugins/on2itsecurity/auxo/0.1/linux_amd64/ ``` + +### Documentation + +The documentation can be build using [tfplugindocs](https://github.com/hashicorp/terraform-plugin-docs). + +```shell +tfplugindocs generate +``` \ No newline at end of file diff --git a/auxo/configfile.go b/auxo/configfile.go new file mode 100644 index 0000000..4c3b49e --- /dev/null +++ b/auxo/configfile.go @@ -0,0 +1,97 @@ +// Description: This file contains functions for reading the (ztctl) config file + +package auxo + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "os" + + "github.com/mitchellh/go-homedir" +) + +type ConfigFile struct { + Configs []ConfigEntry `json:"configs"` +} + +type ConfigEntry struct { + Alias string `json:"alias"` + Description string `json:"description"` + Token string `json:"token"` + APIAddress string `json:"apiaddress"` + Debug bool `json:"debug"` +} + +// Will return the default config file location +func getDefaultConfigLocation() string { + // Find home directory. + home, err := homedir.Dir() + if err != nil { + log.Fatal(err) + os.Exit(1) + } + + location := home + "/.ztctl/" + "config.json" + + return location +} + +// GetConfigs will get all config entries from the specified file +// Specify filename for different location, leave empty for default. +// returns ConfigFile and error +func getConfigs(fileName string) (ConfigFile, error) { + + //Read full config and select the Alias + cfgFile := ConfigFile{} + cfgFileAsByte, err := readFile(fileName) + if err != nil { + return ConfigFile{}, err + } + + err = json.Unmarshal(cfgFileAsByte, &cfgFile) + if err != nil { + return ConfigFile{}, err + } + + return cfgFile, nil +} + +// Read file and return byte array or error +func readFile(fileName string) ([]byte, error) { + f, err := os.Open(fileName) + + if err != nil { + return nil, err + } + + defer f.Close() + fileContent, err := ioutil.ReadAll(f) + + if err != nil { + return nil, err + } + + return fileContent, nil +} + +// GetConfig will get the config entry with the specified alias +// Specify filename for different location, leave empty for default. +// returns ConfigEntry and error +func getConfig(filename, alias string) (ConfigEntry, error) { + cfg, err := getConfigs(filename) + + if err != nil { + return ConfigEntry{}, err + } + + //Find the config entry with the alias + for _, ce := range cfg.Configs { + if ce.Alias == alias { + return ce, nil + } + } + + return ConfigEntry{}, fmt.Errorf("Could not find config entry with alias %s", alias) +} diff --git a/auxo/datasource_contact.go b/auxo/datasource_contact.go index 7321f09..b89ea80 100644 --- a/auxo/datasource_contact.go +++ b/auxo/datasource_contact.go @@ -3,49 +3,98 @@ package auxo import ( "context" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/on2itsecurity/go-auxo" ) -func dataSourceContact() *schema.Resource { - return &schema.Resource{ - ReadContext: dataSourceContactRead, - Schema: map[string]*schema.Schema{ - "id": { - Type: schema.TypeString, - Computed: true, +// Ensure the implementation satisfies the expected interfaces. +var ( + _ datasource.DataSource = &contactDataSource{} + _ datasource.DataSourceWithConfigure = &contactDataSource{} +) + +type contactDataSource struct { + client *auxo.Client +} + +type contactDataSourceModel struct { + ID types.String `tfsdk:"id"` + Email types.String `tfsdk:"email"` +} + +// NewcontactDataSource is a helper function to simplify the provider implementation. +func NewcontactDataSource() datasource.DataSource { + return &contactDataSource{} +} + +// Metadata returns the data source type name. +func (d *contactDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_contact" +} + +func (d *contactDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, _ *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + // Retrieve the client from the provider config + d.client = req.ProviderData.(*auxo.Client) +} + +// Schema defines the schema for the data source. +func (d *contactDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "A contact which can be used a.o. as main- or securitycontact in a `protectsurface`.", + MarkdownDescription: "A contact which can be used a.o. as main- or securitycontact in a `protectsurface`.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Computed unique IDs of the contact", + MarkdownDescription: "Computed unique IDs of the contact", + Computed: true, }, - "email": { - Type: schema.TypeString, - Required: true, + "email": schema.StringAttribute{ + Description: "Emails of the contact", + MarkdownDescription: "Emails of the contact", + Required: true, }, }, } } -func dataSourceContactRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics - - provider := m.(*AuxoProvider) - apiClient := provider.APIClient - contacts, err := apiClient.CRM.GetContacts() +// Read refreshes the Terraform state with the latest data. +func (d *contactDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var state contactDataSourceModel + //Get contacts + contacts, err := d.client.CRM.GetContacts() if err != nil { - return diag.FromErr(err) + resp.Diagnostics.AddError("Unable to retrieve contacts", err.Error()) + return + } + + //Get input + var input contactDataSourceModel + diags := req.Config.Get(ctx, &input) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } + //Find the contact for _, c := range contacts { - if c.Email == d.Get("email").(string) { - d.SetId(c.ID) - d.Set("id", c.ID) - d.Set("email", c.Email) + if c.Email == input.Email.ValueString() { + state.ID = types.StringValue(c.ID) + state.Email = types.StringValue(c.Email) break } } - if d.Id() == "" { - return diag.Errorf("Contact not found [%s]", d.Get("email").(string)) + //set state + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } - - return diags } diff --git a/auxo/provider.go b/auxo/provider.go index e000d1a..1f85227 100644 --- a/auxo/provider.go +++ b/auxo/provider.go @@ -2,70 +2,153 @@ package auxo import ( "context" + "os" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" "github.com/on2itsecurity/go-auxo" ) -// AuxoProvider represents the Auxo provider -type AuxoProvider struct { - APIClient *auxo.Client +// Ensure the implementation satisfies the provider.Provider interface. +var _ provider.Provider = (*auxoProvider)(nil) + +// auxoProvider represents the provider.Provider interface +type auxoProvider struct{} + +type auxoProviderModel struct { + Url types.String `tfsdk:"url"` + Token types.String `tfsdk:"token"` + Name types.String `tfsdk:"name"` + Config types.String `tfsdk:"config"` +} + +// New returns a new provider.Provider. +func New() provider.Provider { + return &auxoProvider{} } -// Provider - -func Provider() *schema.Provider { - return &schema.Provider{ - Schema: map[string]*schema.Schema{ - "url": { - Type: schema.TypeString, - Optional: true, - Default: "api.on2it.net", +func (p *auxoProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "auxo" + resp.Version = "dev" +} + +func (p *auxoProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "The alias in the ztctl configuration file, this takes precedence over the url and token attributes", + Description: "The alias in the ztctl configuration file, this takes precedence over the url and token attributes", }, - "token": { - Type: schema.TypeString, - Required: true, - Sensitive: true, - DefaultFunc: schema.EnvDefaultFunc("AUXOTOKEN", ""), + "config": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Location of the ztctl configuration file, will default to `~/.ztctl/config.json`", + Description: "Location of the ztctl configuration file, will default to `~/.ztctl/config.json`", + }, + "url": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "The URL of the Auxo API", + Description: "The URL of the Auxo API", + }, + "token": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "The token to access the API", + Description: "The token to access the API", + Sensitive: true, }, }, - DataSourcesMap: map[string]*schema.Resource{ - "auxo_contact": dataSourceContact(), - }, - ResourcesMap: map[string]*schema.Resource{ - "auxo_location": resourceLocation(), - "auxo_protectsurface": resourceProtectSurface(), - "auxo_state": resourceState(), - "auxo_transactionflow": resourceTransactionFlow(), - }, - ConfigureContextFunc: providerConfigure, } } -func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { - url := d.Get("url").(string) - token := d.Get("token").(string) +func (p *auxoProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + //Default or environment variables + // checkov:skip=CKV_SECRET_6: False/Positive + token := os.Getenv("AUXO_TOKEN") + url := "api.on2it.net" + config := getDefaultConfigLocation() + + var data auxoProviderModel + + // Read configuration data into model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) - // Warning or errors can be collected in a slice type - var diags diag.Diagnostics + // Check configuration data, which should take precedence over + // environment variable data, if found. + if data.Token.ValueString() != "" { + token = data.Token.ValueString() + } + + if data.Url.ValueString() != "" { + url = data.Url.ValueString() + } + + if data.Config.ValueString() != "" { + config = data.Config.ValueString() + } + + if data.Name.ValueString() != "" { + alias := data.Name.ValueString() + + //Read configuration and set url and token + cfg, err := getConfig(config, alias) - if token != "" { - client, err := auxo.NewClient(url, token, false) if err != nil { - return nil, diag.FromErr(err) + resp.Diagnostics.AddError( + "Unable to read configuration file", + "An unexpected error occurred when reading the configuration file. "+ + "client error: "+err.Error()) } - provider := new(AuxoProvider) - provider.APIClient = client + //Find specific alias and set url and token + url = cfg.APIAddress + token = cfg.Token + } + + //Error checking + if token == "" { + resp.Diagnostics.AddError( + "Missing API Token Configuration", + "While configuring the provider, the API token was not found in "+ + "the AUXO_TOKEN environment variable or provider "+ + "configuration block 'token' or 'config' attribute.", + ) + } + + if url == "" { + resp.Diagnostics.AddError( + "Missing API URL Configuration", + "While configuring the provider, the API URL was not found in "+ + "the provider configuration block 'url' or 'config' attribute.", + ) + } - return provider, diags + // Create data/clients and persist to resp.DataSourceData and + // resp.ResourceData as appropriate. + auxoClient, err := auxo.NewClient(url, token, false) + if err != nil { + resp.Diagnostics.AddError("Unable to create AUXO API client", + "An unexpected error occurred when creating the AUXO API client. "+ + "client error: "+err.Error()) } - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "No token specified.", - Detail: "Please set the token in the configuration or as environment variable AUXOTOKEN.", - }) + resp.DataSourceData = auxoClient + resp.ResourceData = auxoClient +} - return nil, diags //Will never be hit +func (p *auxoProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + NewProtectsurfaceResource, + NewLocationResource, + NewStateResource, + NewTransactionflowResource, + } +} + +func (p *auxoProvider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + NewcontactDataSource, + } } diff --git a/auxo/resource_location.go b/auxo/resource_location.go index 59eca6c..a45adcb 100644 --- a/auxo/resource_location.go +++ b/auxo/resource_location.go @@ -3,151 +3,235 @@ package auxo import ( "context" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/float64default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/on2itsecurity/go-auxo" "github.com/on2itsecurity/go-auxo/zerotrust" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func resourceLocation() *schema.Resource { - return &schema.Resource{ - CreateContext: resourceLocationCreate, - ReadContext: resourceLocationRead, - UpdateContext: resourceLocationUpdate, - DeleteContext: resourceLocationDelete, - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - Schema: map[string]*schema.Schema{ - "id": { - Type: schema.TypeString, - Computed: true, - Description: "Unique ID of the resource/location", +// Ensure the implementation satisfies the resource.Resource interface. +var _ resource.Resource = &locationResource{} + +type locationResource struct { + client *auxo.Client +} + +type locationResourceModel struct { + ID types.String `tfsdk:"id"` + Uniqueness_key types.String `tfsdk:"uniqueness_key"` + Name types.String `tfsdk:"name"` + Latitude types.Float64 `tfsdk:"latitude"` + Longitude types.Float64 `tfsdk:"longitude"` +} + +func NewLocationResource() resource.Resource { + return &locationResource{} +} + +func (r *locationResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_location" +} + +func (r *locationResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + // Retrieve the client from the provider config + r.client = req.ProviderData.(*auxo.Client) +} + +func (r *locationResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "A location which can be used in a state to reflect where resources are located.", + MarkdownDescription: "A location which can be used in a state to reflect where resources are located.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Computed unique ID of the resource location", + MarkdownDescription: "Computed unique ID of the resource location", + Required: false, + Optional: false, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, - "uniqueness_key": { - Type: schema.TypeString, - Computed: true, - Optional: true, - ForceNew: true, - Description: "Unique key to generate the ID - only needed for parallel import", + "uniqueness_key": schema.StringAttribute{ + Description: "Custom and optinal uniqueness key to identify the resource location", + MarkdownDescription: "Custom and optinal uniqueness key to identify the resource location", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + stringplanmodifier.UseStateForUnknown(), + }, }, - "name": { - Type: schema.TypeString, - Required: true, - Description: "Unique name of the location", + "name": schema.StringAttribute{ + Description: "Name of the resource location", + MarkdownDescription: "Name of the resource location", + Required: true, }, - "latitude": { - Type: schema.TypeFloat, - Optional: true, - Description: "Latitude of the location", + "latitude": schema.Float64Attribute{ + Description: "Latitude of the resource location", + MarkdownDescription: "Latitude of the resource location", + Optional: true, + Computed: true, + Default: float64default.StaticFloat64(0), }, - "longitude": { - Type: schema.TypeFloat, - Optional: true, - Description: "Longitude of the location", + "longitude": schema.Float64Attribute{ + Description: "Longitude of the resource location", + MarkdownDescription: "Longitude of the resource location", + Optional: true, + Computed: true, + Default: float64default.StaticFloat64(0), }, }, } } -func resourceLocationCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics +func (r *locationResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + //Retrieve values from plan + var plan locationResourceModel - provider := m.(*AuxoProvider) - apiClient := provider.APIClient + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) - var location = new(zerotrust.Location) + if resp.Diagnostics.HasError() { + return + } - location.UniquenessKey = d.Get("uniqueness_key").(string) - location.Name = d.Get("name").(string) - location.Coords.Latitude = d.Get("latitude").(float64) - location.Coords.Longitude = d.Get("longitude").(float64) + // Create location (object) + location := resourceModelToLocation(&plan) - result, err := apiClient.ZeroTrust.CreateLocationByObject(*location, false) + // Create location (API) + result, err := r.client.ZeroTrust.CreateLocationByObject(location, false) if err != nil { - return diag.FromErr(err) + resp.Diagnostics.AddError("Error creating location", "unexpected error: "+err.Error()) + return } - d.SetId(result.ID) + // Map resonse to schema + plan = locationToResourceModel(result) - resourceLocationRead(ctx, d, m) + // Set state + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) - return diags + if resp.Diagnostics.HasError() { + return + } } -func resourceLocationRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics - - provider := m.(*AuxoProvider) - apiClient := provider.APIClient - - location, err := apiClient.ZeroTrust.GetLocationByID(d.Id()) +func (r *locationResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var location locationResourceModel + diags := req.State.Get(ctx, &location) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + // Get refreshed location from AUXO + result, err := r.client.ZeroTrust.GetLocationByID(location.ID.ValueString()) if err != nil { apiError := getAPIError(err) - //NotExists - if apiError.ID == "410" { - d.SetId("") - return nil + if apiError.ID == "410" { // Location not found and probably deleted + resp.State.RemoveResource(ctx) + return + } else { + resp.Diagnostics.AddError("Error reading location", "unexpected error: "+err.Error()) + return } - return diag.FromErr(err) } - d.Set("id", location.ID) - d.Set("uniqueness_key", location.UniquenessKey) - d.Set("name", location.Name) - d.Set("latitude", location.Coords.Latitude) - d.Set("longitude", location.Coords.Longitude) + //Overwrite state with refreshed location + location = locationToResourceModel(result) - return diags + //Set refreshed state + diags = resp.State.Set(ctx, &location) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } } -func resourceLocationUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - provider := m.(*AuxoProvider) - apiClient := provider.APIClient +func (r *locationResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + //Retrieve values from plan + var plan, state locationResourceModel - location, err := apiClient.ZeroTrust.GetLocationByID(d.Id()) + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + diags = req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) - if err != nil { - return diag.FromErr(err) + if resp.Diagnostics.HasError() { + return } - if d.HasChange("uniqueness_key") { - location.UniquenessKey = d.Get("uniqueness_key").(string) - } - if d.HasChange("name") { - location.Name = d.Get("name").(string) - } - if d.HasChange("latitude") { - location.Coords.Latitude = d.Get("latitude").(float64) - } - if d.HasChange("longitude") { - location.Coords.Longitude = d.Get("longitude").(float64) - } + // Create location (object) + location := resourceModelToLocation(&plan) - _, err = apiClient.ZeroTrust.UpdateLocation(*location) + // Update location (API) + result, err := r.client.ZeroTrust.UpdateLocation(location) if err != nil { - return diag.FromErr(err) + resp.Diagnostics.AddError("Error updating location", "unexpected error: "+err.Error()) + return } - return resourceLocationRead(ctx, d, m) -} + // Update state + plan = locationToResourceModel(result) + diags = resp.State.Set(ctx, plan) -func resourceLocationDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - provider := m.(*AuxoProvider) - apiClient := provider.APIClient + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} - var diags diag.Diagnostics +func (r *locationResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + //Retrieve values from state + var location locationResourceModel + diags := req.State.Get(ctx, &location) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } - err := apiClient.ZeroTrust.DeleteLocationByID(d.Id()) + //Delete location + err := r.client.ZeroTrust.DeleteLocationByID(location.ID.ValueString()) if err != nil { - return diag.FromErr(err) + resp.Diagnostics.AddError("Error deleting location", "unexpected error: "+err.Error()) + return } +} - d.SetId("") +// resourceModelToLocation maps the resource model to the zerotrust.location object +func resourceModelToLocation(m *locationResourceModel) zerotrust.Location { + return zerotrust.Location{ + ID: m.ID.ValueString(), + UniquenessKey: m.Uniqueness_key.ValueString(), + Name: m.Name.ValueString(), + Coords: zerotrust.Coords{ + Latitude: m.Latitude.ValueFloat64(), + Longitude: m.Longitude.ValueFloat64(), + }, + } +} - return diags +// locationToResouceModel maps the zerotrust.location object to the resource model +func locationToResourceModel(location *zerotrust.Location) locationResourceModel { + return locationResourceModel{ + ID: types.StringValue(location.ID), + Uniqueness_key: types.StringValue(location.UniquenessKey), + Name: types.StringValue(location.Name), + Latitude: types.Float64Value(location.Coords.Latitude), + Longitude: types.Float64Value(location.Coords.Longitude), + } } diff --git a/auxo/resource_protectsurface.go b/auxo/resource_protectsurface.go index 32079ca..18c7b7b 100644 --- a/auxo/resource_protectsurface.go +++ b/auxo/resource_protectsurface.go @@ -2,202 +2,342 @@ package auxo import ( "context" - "sort" "strings" "time" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/on2itsecurity/go-auxo" "github.com/on2itsecurity/go-auxo/zerotrust" ) -func resourceProtectSurface() *schema.Resource { - return &schema.Resource{ - CreateContext: resourceProtectSurfaceCreate, - ReadContext: resourceProtectSurfaceRead, - UpdateContext: resourceProtectSurfaceUpdate, - DeleteContext: resourceProtectSurfaceDelete, - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - Schema: map[string]*schema.Schema{ - "id": { - Type: schema.TypeString, - Computed: true, - Description: "Unique ID of the resource/segment", +// var _ resource.ResourceWithModifyPlan = &protectsurfaceResource{} +var _ resource.Resource = &protectsurfaceResource{} + +type protectsurfaceResource struct { + client *auxo.Client +} + +type protectsurfaceResourceModel struct { + ID types.String `tfsdk:"id"` + Uniqueness_key types.String `tfsdk:"uniqueness_key"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + MainContact types.String `tfsdk:"main_contact"` + SecurityContact types.String `tfsdk:"security_contact"` + InControlBoundary types.Bool `tfsdk:"in_control_boundary"` + InZeroTrustFocus types.Bool `tfsdk:"in_zero_trust_focus"` + Relevance types.Int64 `tfsdk:"relevance"` + Confidentiality types.Int64 `tfsdk:"confidentiality"` + Integrity types.Int64 `tfsdk:"integrity"` + Availability types.Int64 `tfsdk:"availability"` + DataTags types.Set `tfsdk:"data_tags"` + ComplianceTags types.Set `tfsdk:"compliance_tags"` + CustomerLabels types.Map `tfsdk:"customer_labels"` + SOCTags types.Set `tfsdk:"soc_tags"` + AllowFlowsFromOutside types.Bool `tfsdk:"allow_flows_from_outside"` + AllowFlowsToOutside types.Bool `tfsdk:"allow_flows_to_outside"` + MaturityStep1 types.Int64 `tfsdk:"maturity_step1"` + MaturityStep2 types.Int64 `tfsdk:"maturity_step2"` + MaturityStep3 types.Int64 `tfsdk:"maturity_step3"` + MaturityStep4 types.Int64 `tfsdk:"maturity_step4"` + MaturityStep5 types.Int64 `tfsdk:"maturity_step5"` + Measures map[string]measure `tfsdk:"measures"` +} + +type measure struct { + //Measure types.String `tfsdk:"measure"` + Assigned types.Bool `tfsdk:"assigned"` + Assigned_by types.String `tfsdk:"assigned_by"` + Assigned_timestamp types.Int64 `tfsdk:"assigned_timestamp"` + Implemented types.Bool `tfsdk:"implemented"` + Implemented_by types.String `tfsdk:"implemented_by"` + Implemented_timestamp types.Int64 `tfsdk:"implemented_timestamp"` + Evidenced types.Bool `tfsdk:"evidenced"` + Evidenced_by types.String `tfsdk:"evidenced_by"` + Evidenced_timestamp types.Int64 `tfsdk:"evidenced_timestamp"` +} + +func NewProtectsurfaceResource() resource.Resource { + return &protectsurfaceResource{} +} + +func (r *protectsurfaceResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_protectsurface" +} + +func (r *protectsurfaceResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + // Retrieve the client from the provider config + r.client = req.ProviderData.(*auxo.Client) +} + +func (r *protectsurfaceResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "A zero trust protectsurface which reflects what you want to protect.", + MarkdownDescription: "A zero trust protectsurface which reflects what you want to protect.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Computed unique ID of the resource protectsurface", + MarkdownDescription: "Computed unique ID of the resource protectsurface", + Required: false, + Optional: false, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, - "uniqueness_key": { - Type: schema.TypeString, - Computed: true, - Optional: true, - ForceNew: true, - Description: "Unique key to generate the ID - only needed for parallel import", + "uniqueness_key": schema.StringAttribute{ + Description: "Custom and optinal uniqueness key to identify the resource protectsurface", + MarkdownDescription: "Custom and optinal uniqueness key to identify the resource protectsurface", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + stringplanmodifier.UseStateForUnknown(), + }, }, - "name": { - Type: schema.TypeString, - Required: true, - Description: "Name of the segment", + "name": schema.StringAttribute{ + Description: "Name of the resource protectsurface", + MarkdownDescription: "Name of the resource protectsurface", + Required: true, }, - "description": { - Type: schema.TypeString, - Optional: true, - Description: "Description of the segment", + "description": schema.StringAttribute{ + Description: "Description of the resource protectsurface", + MarkdownDescription: "Description of the resource protectsurface", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), }, - "main_contact": { - Type: schema.TypeString, - Optional: true, - Description: "ID of main contact in text)", + "main_contact": schema.StringAttribute{ + Description: "Main contact of the resource protectsurface", + MarkdownDescription: "Main contact of the resource protectsurface", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), }, - "security_contact": { - Type: schema.TypeString, - Optional: true, - Description: "ID of security contact in text", + "security_contact": schema.StringAttribute{ + Description: "Security contact of the resource protectsurface", + MarkdownDescription: "Security contact of the resource protectsurface", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), }, - "in_control_boundary": { - Type: schema.TypeBool, - Default: false, - Optional: true, - Description: "Is this protect surface in the control boundary (your responsibility)", + "in_control_boundary": schema.BoolAttribute{ + Description: "This protect surface is within the 'control boundary'", + MarkdownDescription: "This protect surface is within the 'control boundary'", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), }, - "in_zero_trust_focus": { - Type: schema.TypeBool, - Default: false, - Optional: true, - Description: "Is this protect surface in the zero trust focus (actively maintained and monitored)", + "in_zero_trust_focus": schema.BoolAttribute{ + Description: "This protect surface is within the 'zero trust focus' (actively maintained and monitored)", + MarkdownDescription: "This protect surface is within the 'zero trust focus' (actively maintained and monitored)", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), }, - "relevance": { - Type: schema.TypeInt, - Default: 60, - Optional: true, - Description: "Relevance 0-100 of the segment", + "relevance": schema.Int64Attribute{ + Description: "Relevance of the resource protectsurface", + MarkdownDescription: "Relevance of the resource protectsurface", + Required: true, }, - "confidentiality": { - Type: schema.TypeInt, - Optional: true, - Default: 1, - Description: "Confidentiality score (number 1-5)", + "confidentiality": schema.Int64Attribute{ + Description: "Confidentiality of the resource protectsurface", + MarkdownDescription: "Confidentiality of the resource protectsurface", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(1), }, - "integrity": { - Type: schema.TypeInt, - Optional: true, - Default: 1, - Description: "Integrity score (number 1-5)", + "integrity": schema.Int64Attribute{ + Description: "Integrity of the resource protectsurface", + MarkdownDescription: "Integrity of the resource protectsurface", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(1), }, - "availability": { - Type: schema.TypeInt, - Optional: true, - Default: 1, - Description: "Availability score (number 1-5)", + "availability": schema.Int64Attribute{ + Description: "Availability of the resource protectsurface", + MarkdownDescription: "Availability of the resource protectsurface", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(1), }, - "data_tags": { - Type: schema.TypeSet, - Optional: true, - Description: "Contains data tags, defining the data residing in the protect surface", - Elem: &schema.Schema{Type: schema.TypeString}, + "data_tags": schema.SetAttribute{ + Description: "Data tags of the resource protectsurface", + MarkdownDescription: "Data tags of the resource protectsurface", + Optional: true, + Computed: true, + ElementType: types.StringType, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.UseStateForUnknown(), + }, }, - "compliance_tags": { - Type: schema.TypeSet, - Optional: true, - Description: "Contains compliance tags, defining compliancy requirements of the protect surface", - Elem: &schema.Schema{Type: schema.TypeString}, + "compliance_tags": schema.SetAttribute{ + Description: "Compliance tags of the resource protectsurface", + MarkdownDescription: "Compliance tags of the resource protectsurface", + Optional: true, + Computed: true, + ElementType: types.StringType, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.UseStateForUnknown(), + }, }, - "customer_labels": { - Type: schema.TypeMap, - Optional: true, - Description: "Contains customer labels in Key-Value-Pair format", - Elem: &schema.Schema{Type: schema.TypeString}, + "customer_labels": schema.MapAttribute{ + Description: "Customer labels of the resource protectsurface", + MarkdownDescription: "Customer labels of the resource protectsurface", + Optional: true, + Computed: true, + ElementType: types.StringType, + PlanModifiers: []planmodifier.Map{ + mapplanmodifier.UseStateForUnknown(), + }, }, - "soc_tags": { - Type: schema.TypeSet, - Optional: true, - Description: "Contains tags, which are used by the SOC engineers", - Elem: &schema.Schema{Type: schema.TypeString}, + "soc_tags": schema.SetAttribute{ + Description: "SOC tags of the resource protectsurface, only use when advised by the SOC", + MarkdownDescription: "SOC tags of the resource protectsurface, only use when advised by the SOC", + Optional: true, + Computed: true, + ElementType: types.StringType, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.UseStateForUnknown(), + }, }, - "allow_flows_from_outside": { - Type: schema.TypeBool, - Optional: true, - Description: "Does this protect surface allows to have flows from outside (e.g. Internet)", - Default: false, + "allow_flows_from_outside": schema.BoolAttribute{ + Description: "Allow flows from outside of the protectsurface coming in", + MarkdownDescription: "Allow flows from outside of the protectsurface coming in", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), }, - "allow_flows_to_outside": { - Type: schema.TypeBool, - Optional: true, - Description: "Does this protect surface allows to have flows to outside (e.g. Internet)", - Default: false, + "allow_flows_to_outside": schema.BoolAttribute{ + Description: "Allow flows to go outside of the protectsurface", + MarkdownDescription: "Allow flows to go outside of the protectsurface", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), }, - "maturity_step1": { - Type: schema.TypeInt, - Default: 1, - Optional: true, - Description: "maturity step 1 - defining the protect surface", + "maturity_step1": schema.Int64Attribute{ + Description: "Maturity step 1", + MarkdownDescription: "Maturity step 1", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(1), }, - "maturity_step2": { - Type: schema.TypeInt, - Default: 1, - Optional: true, - Description: "maturity step 2 - map the transaction flows", + "maturity_step2": schema.Int64Attribute{ + Description: "Maturity step 2", + MarkdownDescription: "Maturity step 2", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(1), }, - "maturity_step3": { - Type: schema.TypeInt, - Default: 1, - Optional: true, - Description: "maturity step 3 - architect your environment", + "maturity_step3": schema.Int64Attribute{ + Description: "Maturity step 3", + MarkdownDescription: "Maturity step 3", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(1), }, - "maturity_step4": { - Type: schema.TypeInt, - Default: 1, - Optional: true, - Description: "maturity step 4 - zero trust policy", + "maturity_step4": schema.Int64Attribute{ + Description: "Maturity step 4", + MarkdownDescription: "Maturity step 4", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(1), }, - "maturity_step5": { - Type: schema.TypeInt, - Default: 1, - Optional: true, - Description: "maturity step 5 - monitor and maintain", + "maturity_step5": schema.Int64Attribute{ + Description: "Maturity step 5", + MarkdownDescription: "Maturity step 5", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(1), }, - - "measure": { - Type: schema.TypeSet, - Optional: true, - Description: "List of measures set for this protect surface", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "type": { - Type: schema.TypeString, - Required: true, - Description: "type of the measure", + "measures": schema.MapNestedAttribute{ + Description: "Measures of the resource protectsurface", + MarkdownDescription: "Measures of the resource protectsurface", + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "assigned": schema.BoolAttribute{ + Description: "Measure assigned to the protectsurface", + MarkdownDescription: "Measure assigned to the protectsurface", + Required: true, + }, + "assigned_by": schema.StringAttribute{ + Description: "Who assigned this measure to the protectsurface", + MarkdownDescription: "Who assigned this measure to the protectsurface", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), }, - "assigned": { - Type: schema.TypeBool, - Required: true, - Description: "Is this measure assigned to the protect surface", + "assigned_timestamp": schema.Int64Attribute{ + Description: "When was this measure assigned to the protectsurface", + MarkdownDescription: "When was this measure assigned to the protectsurface", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, }, - "assigned_by": { - Type: schema.TypeString, - Optional: true, - Description: "Who assigned this measure to the protect surface", + "implemented": schema.BoolAttribute{ + Description: "Is this measure implemented to the protectsurface", + MarkdownDescription: "Is this measure implemented to the protectsurface", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), }, - "implemented": { - Type: schema.TypeBool, - Optional: true, - Description: "Is this measure implemented to the protect surface", - Default: false, + "implemented_by": schema.StringAttribute{ + Description: "Who implemented this measure to the protectsurface", + MarkdownDescription: "Who implemented this measure to the protectsurface", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), }, - "implemented_by": { - Type: schema.TypeString, - Optional: true, - Description: "Who implemented this measure to the protect surface", + "implemented_timestamp": schema.Int64Attribute{ + Description: "When was this measure implemented to the protectsurface", + MarkdownDescription: "When was this measure implemented to the protectsurface", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, }, - "evidenced": { - Type: schema.TypeBool, - Optional: true, - Description: "Is there evidence that this measure is implemented", - Default: false, + "evidenced": schema.BoolAttribute{ + Description: "Is there evidence that this measure is implemented", + MarkdownDescription: "Is there evidence that this measure is implemented", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), }, - "evidenced_by": { - Type: schema.TypeString, - Optional: true, - Description: "Who evidenced that this measure is implementd", + "evidenced_by": schema.StringAttribute{ + Description: "Who evidenced that this measure is implementd", + MarkdownDescription: "Who evidenced that this measure is implementd", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "evidenced_timestamp": schema.Int64Attribute{ + Description: "When was this measure evidenced", + MarkdownDescription: "When was this measure evidenced", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, }, }, }, @@ -206,249 +346,140 @@ func resourceProtectSurface() *schema.Resource { } } -func resourceProtectSurfaceCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics - - provider := m.(*AuxoProvider) - apiClient := provider.APIClient - - var ps = new(zerotrust.ProtectSurface) - ps.UniquenessKey = d.Get("uniqueness_key").(string) - ps.Name = d.Get("name").(string) - ps.Description = d.Get("description").(string) - ps.MainContactPersonID = d.Get("main_contact").(string) - ps.SecurityContactPersonID = d.Get("security_contact").(string) - ps.InControlBoundary = d.Get("in_control_boundary").(bool) - ps.InZeroTrustFocus = d.Get("in_zero_trust_focus").(bool) - ps.Relevance = d.Get("relevance").(int) - ps.Confidentiality = d.Get("confidentiality").(int) - ps.Integrity = d.Get("integrity").(int) - ps.Availability = d.Get("availability").(int) - ps.DataTags = createStringSliceFromListInput(d.Get("data_tags").(*schema.Set).List()) - ps.ComplianceTags = createStringSliceFromListInput(d.Get("compliance_tags").(*schema.Set).List()) - ps.SocTags = createStringSliceFromListInput(d.Get("soc_tags").(*schema.Set).List()) - cl := make(map[string]string) - for k, v := range d.Get("customer_labels").(map[string]any) { - cl[k] = v.(string) - } - ps.CustomerLabels = cl +func (r *protectsurfaceResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + //Retrieve values from plan + var plan protectsurfaceResourceModel - //Transaction Flows External - ps.FlowsFromOutside = zerotrust.Flow{Allow: d.Get("allow_flows_from_outside").(bool)} - ps.FlowsToOutside = zerotrust.Flow{Allow: d.Get("allow_flows_to_outside").(bool)} + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) - //Maturity - ps.Maturity.Step1 = d.Get("maturity_step1").(int) - ps.Maturity.Step2 = d.Get("maturity_step2").(int) - ps.Maturity.Step3 = d.Get("maturity_step3").(int) - ps.Maturity.Step4 = d.Get("maturity_step4").(int) - ps.Maturity.Step5 = d.Get("maturity_step5").(int) + if resp.Diagnostics.HasError() { + return + } - //Measures + protectsurface, d := resourceModelToProtectsurface(&plan, ctx, r) - measures := d.Get("measure").(*schema.Set).List() - measureMap := make(map[string]zerotrust.MeasureState) + resp.Diagnostics.Append(d...) - availableMeasures, _ := apiClient.ZeroTrust.GetMeasures() - availableMeasuresInSlice := make([]string, 0) - for _, mg := range availableMeasures.Groups { - for _, m := range mg.Measures { - availableMeasuresInSlice = append(availableMeasuresInSlice, m.Name) - } + if resp.Diagnostics.HasError() { + return } - for _, mRaw := range measures { - m := mRaw.(map[string]any) - - //Check if measure exists - if !sliceContains(availableMeasuresInSlice, m["type"].(string)) { - return diag.Errorf("Measure %s does not exist, available measures [%s]", m["type"].(string), strings.Join(availableMeasuresInSlice, ",")) - } + //Create the protectsurface + result, err := r.client.ZeroTrust.CreateProtectSurfaceByObject(protectsurface, false) - assignment := zerotrust.Assignment{ - Assigned: m["assigned"].(bool), - LastDeterminedByPersonID: m["assigned_by"].(string), - LastDeterminedTimestamp: int(time.Now().Unix()), - } + if err != nil { + resp.Diagnostics.AddError("Error creating protect surface", "unexpected error: "+err.Error()) + return + } - implementation := zerotrust.Implementation{ - Implemented: m["implemented"].(bool), - LastDeterminedByPersonID: m["implemented_by"].(string), - LastDeterminedTimestamp: int(time.Now().Unix()), - } + //Map response to schema + plan, _ = protectsurfaceToResourceModel(result, ctx) - evidence := zerotrust.Evidence{ - Evidenced: m["evidenced"].(bool), - LastDeterminedByPersonID: m["evidenced_by"].(string), - LastDeterminedTimestamp: int(time.Now().Unix()), - } + // Set state + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) - measureMap[m["type"].(string)] = zerotrust.MeasureState{ - Assignment: &assignment, - Implementation: &implementation, - Evidence: &evidence, - } + if resp.Diagnostics.HasError() { + return } +} - ps.Measures = measureMap - - result, err := apiClient.ZeroTrust.CreateProtectSurfaceByObject(*ps, false) +func (r *protectsurfaceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var state protectsurfaceResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + // Get refreshed PS from AUXO + result, err := r.client.ZeroTrust.GetProtectSurfaceByID(state.ID.ValueString()) if err != nil { - return diag.FromErr(err) + apiError := getAPIError(err) + + if apiError.ID == "410" { // Location not found and probably deleted + resp.State.RemoveResource(ctx) + return + } else { + resp.Diagnostics.AddError("Error reading location", "unexpected error: "+err.Error()) + return + } } - d.SetId(result.ID) + //Overwrite state with refreshed PS + state, _ = protectsurfaceToResourceModel(result, ctx) - resourceProtectSurfaceRead(ctx, d, m) + //Set refreshed state + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } - return diags } -func resourceProtectSurfaceRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics +func (r *protectsurfaceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - provider := m.(*AuxoProvider) - apiClient := provider.APIClient + // Retrieve values from plan + var plan protectsurfaceResourceModel - ps, err := apiClient.ZeroTrust.GetProtectSurfaceByID(d.Id()) + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) - if err != nil { - apiError := getAPIError(err) + if resp.Diagnostics.HasError() { + return + } - //NotExists - if apiError.ID == "410" { - d.SetId("") - return nil - } + protectsurface, d := resourceModelToProtectsurface(&plan, ctx, r) + + resp.Diagnostics.Append(d...) - return diag.FromErr(err) + if resp.Diagnostics.HasError() { + return } - d.Set("id", ps.ID) - d.Set("uniqueness_key", ps.UniquenessKey) - d.Set("name", ps.Name) - d.Set("description", ps.Description) - d.Set("main_contact", ps.MainContactPersonID) - d.Set("security_contact", ps.SecurityContactPersonID) - d.Set("in_control_boundary", ps.InControlBoundary) - d.Set("in_zero_trust_focus", ps.InZeroTrustFocus) - d.Set("relevance", ps.Relevance) - d.Set("confidentiality", ps.Confidentiality) - d.Set("integrity", ps.Integrity) - d.Set("availability", ps.Availability) - d.Set("data_tags", ps.DataTags) - d.Set("compliance_tags", ps.ComplianceTags) - d.Set("customer_labels", ps.CustomerLabels) - d.Set("soc_tags", ps.SocTags) - - //Transaction Flows External - d.Set("allow_flows_from_outside", ps.FlowsFromOutside.Allow) - d.Set("allow_flows_to_outside", ps.FlowsToOutside.Allow) - - //Maturity - d.Set("maturity_step1", ps.Maturity.Step1) - d.Set("maturity_step2", ps.Maturity.Step2) - d.Set("maturity_step3", ps.Maturity.Step3) - d.Set("maturity_step4", ps.Maturity.Step4) - d.Set("maturity_step5", ps.Maturity.Step5) - - //Measures - flattenedMeasures := flattenMeasures(ps.Measures) - if err := d.Set("measure", flattenedMeasures); err != nil { - return diag.Errorf("error setting measure: %v", err) + result, err := r.client.ZeroTrust.UpdateProtectSurface(protectsurface) + + if err != nil { + resp.Diagnostics.AddError("Error updating protect surface", "unexpected error: "+err.Error()) + return } - return diags + plan, _ = protectsurfaceToResourceModel(result, ctx) + + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } } -func resourceProtectSurfaceUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - provider := m.(*AuxoProvider) - apiClient := provider.APIClient +func (r *protectsurfaceResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - ps, err := apiClient.ZeroTrust.GetProtectSurfaceByID(d.Id()) + // Retrieve values from state + var ps protectsurfaceResourceModel - if err != nil { - return diag.FromErr(err) - } + diags := req.State.Get(ctx, &ps) + resp.Diagnostics.Append(diags...) - if d.HasChange("uniqueness_key") { - ps.UniquenessKey = d.Get("uniqueness_key").(string) - } - if d.HasChange("name") { - ps.Name = d.Get("name").(string) - } - if d.HasChange("description") { - ps.Description = d.Get("description").(string) - } - if d.HasChange("main_contact") { - ps.MainContactPersonID = d.Get("main_contact").(string) - } - if d.HasChange("security_contact") { - ps.SecurityContactPersonID = d.Get("security_contact").(string) - } - if d.HasChange("in_control_boundary") { - ps.InControlBoundary = d.Get("in_control_boundary").(bool) - } - if d.HasChange("in_zero_trust_focus") { - ps.InZeroTrustFocus = d.Get("in_zero_trust_focus").(bool) - } - if d.HasChange("relevance") { - ps.Relevance = d.Get("relevance").(int) - } - if d.HasChange("confidentiality") { - ps.Confidentiality = d.Get("confidentiality").(int) - } - if d.HasChange("integrity") { - ps.Integrity = d.Get("integrity").(int) - } - if d.HasChange("availability") { - ps.Availability = d.Get("availability").(int) - } - if d.HasChange("data_tags") { - ps.DataTags = createStringSliceFromListInput(d.Get("data_tags").(*schema.Set).List()) - } - if d.HasChange("compliance_tags") { - ps.ComplianceTags = createStringSliceFromListInput(d.Get("compliance_tags").(*schema.Set).List()) - } - if d.HasChange("customer_labels") { - cl := make(map[string]string) - for k, v := range d.Get("customer_labels").(map[string]any) { - cl[k] = v.(string) - } - ps.CustomerLabels = cl - } - if d.HasChange("soc_tags") { - ps.SocTags = createStringSliceFromListInput(d.Get("soc_tags").(*schema.Set).List()) + if resp.Diagnostics.HasError() { + return } - //Transaction Flows External - if d.HasChange("allow_flows_from_outside") { - ps.FlowsFromOutside.Allow = d.Get("allow_flows_from_outside").(bool) - } - if d.HasChange("allow_flows_to_outside") { - ps.FlowsToOutside.Allow = d.Get("allow_flows_to_outside").(bool) - } + err := r.client.ZeroTrust.DeleteProtectSurfaceByID(ps.ID.ValueString()) - //Maturity - if d.HasChange("maturity_step1") { - ps.Maturity.Step1 = d.Get("maturity_step1").(int) - } - if d.HasChange("maturity_step2") { - ps.Maturity.Step2 = d.Get("maturity_step2").(int) - } - if d.HasChange("maturity_step3") { - ps.Maturity.Step3 = d.Get("maturity_step3").(int) - } - if d.HasChange("maturity_step4") { - ps.Maturity.Step4 = d.Get("maturity_step4").(int) - } - if d.HasChange("maturity_step5") { - ps.Maturity.Step5 = d.Get("maturity_step5").(int) + if err != nil { + resp.Diagnostics.AddError("Error deleting protect surface", "unexpected error: "+err.Error()) + return } - //Measures //TODO would be nice to have a has-change per item per measure - availableMeasures, _ := apiClient.ZeroTrust.GetMeasures() +} + +func (r *protectsurfaceResource) getAvailableMeasures() []string { + availableMeasures, _ := r.client.ZeroTrust.GetMeasures() availableMeasuresInSlice := make([]string, 0) for _, mg := range availableMeasures.Groups { for _, m := range mg.Measures { @@ -456,125 +487,191 @@ func resourceProtectSurfaceUpdate(ctx context.Context, d *schema.ResourceData, m } } - if d.HasChange("measure") { - changedMeasures := make(map[string]zerotrust.MeasureState) - measures := d.Get("measure").(*schema.Set).List() - - //Limit drifting in set - for _, mRaw := range measures { - m := mRaw.(map[string]any) - - assignment := zerotrust.Assignment{ - Assigned: m["assigned"].(bool), - LastDeterminedByPersonID: m["assigned_by"].(string), - LastDeterminedTimestamp: int(time.Now().Unix()), - } - - implementation := zerotrust.Implementation{ - Implemented: m["implemented"].(bool), - LastDeterminedByPersonID: m["implemented_by"].(string), - LastDeterminedTimestamp: int(time.Now().Unix()), - } - - evidence := zerotrust.Evidence{ - Evidenced: m["evidenced"].(bool), - LastDeterminedByPersonID: m["evidenced_by"].(string), - LastDeterminedTimestamp: int(time.Now().Unix()), - } - - changedMeasures[m["type"].(string)] = zerotrust.MeasureState{ - Assignment: &assignment, - Implementation: &implementation, - Evidence: &evidence, - } - } - - keys := []string{} - for k := range changedMeasures { - keys = append(keys, k) - } - sort.Strings(keys) - //--- - - for _, k := range keys { - - //Check if measure exists - if !sliceContains(availableMeasuresInSlice, k) { - return diag.Errorf("Measure %s does not exist, available measures [%s]", k, strings.Join(availableMeasuresInSlice, ",")) - } - - if ps.Measures[k].Assignment.Assigned != changedMeasures[k].Assignment.Assigned { - changedMeasures[k].Assignment.LastDeterminedTimestamp = int(time.Now().Unix()) - } else { - changedMeasures[k].Assignment.LastDeterminedTimestamp = ps.Measures[k].Assignment.LastDeterminedTimestamp - } - - if ps.Measures[k].Implementation.Implemented != changedMeasures[k].Implementation.Implemented { - changedMeasures[k].Implementation.LastDeterminedTimestamp = int(time.Now().Unix()) - } else { - changedMeasures[k].Implementation.LastDeterminedTimestamp = ps.Measures[k].Implementation.LastDeterminedTimestamp - } - - if ps.Measures[k].Evidence.Evidenced != changedMeasures[k].Evidence.Evidenced { - changedMeasures[k].Evidence.LastDeterminedTimestamp = int(time.Now().Unix()) - } else { - changedMeasures[k].Evidence.LastDeterminedTimestamp = ps.Measures[k].Evidence.LastDeterminedTimestamp - } + return availableMeasuresInSlice +} +func getMeasuresFromMap(measureMap map[string]zerotrust.MeasureState) map[string]measure { + if len(measureMap) == 0 { + return nil + } + + measures := make(map[string]measure, len(measureMap)) + + for k, state := range measureMap { + measures[k] = measure{ + //Measure: types.StringValue(m), + Assigned: types.BoolValue(state.Assignment.Assigned), + Assigned_by: types.StringValue(state.Assignment.LastDeterminedByPersonID), + Assigned_timestamp: types.Int64Value(int64(state.Assignment.LastDeterminedTimestamp)), + Implemented: types.BoolValue(state.Implementation.Implemented), + Implemented_by: types.StringValue(state.Implementation.LastDeterminedByPersonID), + Implemented_timestamp: types.Int64Value(int64(state.Implementation.LastDeterminedTimestamp)), + Evidenced: types.BoolValue(state.Evidence.Evidenced), + Evidenced_by: types.StringValue(state.Evidence.LastDeterminedByPersonID), + Evidenced_timestamp: types.Int64Value(int64(state.Evidence.LastDeterminedTimestamp)), } - ps.Measures = changedMeasures - } - _, err = apiClient.ZeroTrust.UpdateProtectSurface(*ps) + return measures +} - if err != nil { - return diag.FromErr(err) +// resourceModelToProtectsurface maps the resource model to the zerotrust.protectsurface object +func resourceModelToProtectsurface(plan *protectsurfaceResourceModel, ctx context.Context, r *protectsurfaceResource) (zerotrust.ProtectSurface, diag.Diagnostics) { + var diag diag.Diagnostics + var st, dt, ct []string + + if !plan.DataTags.IsNull() { + _ = plan.DataTags.ElementsAs(ctx, &dt, false) + } + if !plan.ComplianceTags.IsNull() { + _ = plan.ComplianceTags.ElementsAs(ctx, &ct, false) + } + if !plan.SOCTags.IsNull() { + _ = plan.SOCTags.ElementsAs(ctx, &st, false) + } + var cl map[string]string + types.Map.ElementsAs(plan.CustomerLabels, ctx, &cl, false) + + //Create the protectsurface object + protectsurface := zerotrust.ProtectSurface{ + ID: plan.ID.ValueString(), + UniquenessKey: plan.Uniqueness_key.ValueString(), + Name: plan.Name.ValueString(), + Description: plan.Description.ValueString(), + MainContactPersonID: plan.MainContact.ValueString(), + SecurityContactPersonID: plan.SecurityContact.ValueString(), + InControlBoundary: plan.InControlBoundary.ValueBool(), + InZeroTrustFocus: plan.InZeroTrustFocus.ValueBool(), + Relevance: int(plan.Relevance.ValueInt64()), + Confidentiality: int(plan.Confidentiality.ValueInt64()), + Integrity: int(plan.Integrity.ValueInt64()), + Availability: int(plan.Availability.ValueInt64()), + DataTags: dt, + ComplianceTags: ct, + CustomerLabels: cl, + SocTags: st, + FlowsFromOutside: zerotrust.Flow{ + Allow: plan.AllowFlowsFromOutside.ValueBool(), + }, + FlowsToOutside: zerotrust.Flow{ + Allow: plan.AllowFlowsToOutside.ValueBool(), + }, + Maturity: zerotrust.Maturity{ + Step1: int(plan.MaturityStep1.ValueInt64()), + Step2: int(plan.MaturityStep2.ValueInt64()), + Step3: int(plan.MaturityStep3.ValueInt64()), + Step4: int(plan.MaturityStep4.ValueInt64()), + Step5: int(plan.MaturityStep5.ValueInt64()), + }, } - return resourceProtectSurfaceRead(ctx, d, m) -} + measureMap := make(map[string]zerotrust.MeasureState, 0) -func resourceProtectSurfaceDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - provider := m.(*AuxoProvider) - apiClient := provider.APIClient + //Loop through measures + for k, m := range plan.Measures { - var diags diag.Diagnostics + //Check if measure exists + if !sliceContains(r.getAvailableMeasures(), k) { + diag.AddError("Measure does not exists.", + "Messure ["+k+"] does not exist, available measures ["+strings.Join(r.getAvailableMeasures(), ",")+"]") + return zerotrust.ProtectSurface{}, diag + } - err := apiClient.ZeroTrust.DeleteProtectSurfaceByID(d.Id()) + var assigned_timestamp int + if !(m.Assigned_timestamp.IsUnknown() || m.Assigned_timestamp.IsNull()) { + assigned_timestamp = int(m.Assigned_timestamp.ValueInt64()) + } else { + assigned_timestamp = int(time.Now().Unix()) + } - if err != nil { - return diag.FromErr(err) - } + assignment := zerotrust.Assignment{ + Assigned: m.Assigned.ValueBool(), + LastDeterminedByPersonID: m.Assigned_by.ValueString(), + LastDeterminedTimestamp: assigned_timestamp, + } - d.SetId("") + var implemented_timestamp int + if !(m.Implemented_timestamp.IsUnknown() || m.Implemented_timestamp.IsNull()) { + implemented_timestamp = int(m.Implemented_timestamp.ValueInt64()) + } else { + implemented_timestamp = int(time.Now().Unix()) + } - return diags -} + implementation := zerotrust.Implementation{ + Implemented: m.Implemented.ValueBool(), + LastDeterminedByPersonID: m.Implemented_by.ValueString(), + LastDeterminedTimestamp: implemented_timestamp, + } -// Flatten Measures so it can be assigend to the resource -func flattenMeasures(measures map[string]zerotrust.MeasureState) []map[string]interface{} { - result := make([]map[string]interface{}, 0) + var evidenced_timestamp int + if !(m.Evidenced_timestamp.IsUnknown() || m.Evidenced_timestamp.IsNull()) { + evidenced_timestamp = int(m.Evidenced_timestamp.ValueInt64()) + } else { + evidenced_timestamp = int(time.Now().Unix()) + } + + evidence := zerotrust.Evidence{ + Evidenced: m.Evidenced.ValueBool(), + LastDeterminedByPersonID: m.Evidenced_by.ValueString(), + LastDeterminedTimestamp: evidenced_timestamp, + } - //Limit drifting in the set when read/update - keys := []string{} - for k := range measures { - keys = append(keys, k) + measureMap[k] = zerotrust.MeasureState{ + Assignment: &assignment, + Implementation: &implementation, + Evidence: &evidence, + } } - sort.Strings(keys) - for _, k := range keys { + if len(measureMap) == 0 { + measureMap = nil + } + protectsurface.Measures = measureMap - measure := make(map[string]interface{}, 7) - measure["type"] = k - measure["assigned"] = measures[k].Assignment.Assigned - measure["assigned_by"] = measures[k].Assignment.LastDeterminedByPersonID - measure["implemented"] = measures[k].Implementation.Implemented - measure["implemented_by"] = measures[k].Implementation.LastDeterminedByPersonID - measure["evidenced"] = measures[k].Evidence.Evidenced - measure["evidenced_by"] = measures[k].Evidence.LastDeterminedByPersonID + return protectsurface, diag +} - result = append(result, measure) - } - return result +// protectsurfaceToResourceModel maps the zerotrust.protectsurface object to the resource model +func protectsurfaceToResourceModel(ps *zerotrust.ProtectSurface, ctx context.Context) (protectsurfaceResourceModel, diag.Diagnostics) { + cl, diag := types.MapValueFrom(ctx, types.StringType, ps.CustomerLabels) + + var st, dt, ct basetypes.SetValue + if ps.ComplianceTags != nil { + ct, _ = types.SetValueFrom(ctx, types.StringType, ps.ComplianceTags) + } + if ps.DataTags != nil { + dt, _ = types.SetValueFrom(ctx, types.StringType, ps.DataTags) + } + if ps.SocTags != nil { + st, _ = types.SetValueFrom(ctx, types.StringType, ps.SocTags) + } + + psrm := protectsurfaceResourceModel{ + ID: types.StringValue(ps.ID), + Uniqueness_key: types.StringValue(ps.UniquenessKey), + Name: types.StringValue(ps.Name), + Description: types.StringValue(ps.Description), + MainContact: types.StringValue(ps.MainContactPersonID), + SecurityContact: types.StringValue(ps.SecurityContactPersonID), + InControlBoundary: types.BoolValue(ps.InControlBoundary), + InZeroTrustFocus: types.BoolValue(ps.InZeroTrustFocus), + Relevance: types.Int64Value(int64(ps.Relevance)), + Confidentiality: types.Int64Value(int64(ps.Confidentiality)), + Integrity: types.Int64Value(int64(ps.Integrity)), + Availability: types.Int64Value(int64(ps.Availability)), + DataTags: dt, + ComplianceTags: ct, + CustomerLabels: cl, + SOCTags: st, + AllowFlowsFromOutside: types.BoolValue(ps.FlowsFromOutside.Allow), + AllowFlowsToOutside: types.BoolValue(ps.FlowsToOutside.Allow), + MaturityStep1: types.Int64Value(int64(ps.Maturity.Step1)), + MaturityStep2: types.Int64Value(int64(ps.Maturity.Step2)), + MaturityStep3: types.Int64Value(int64(ps.Maturity.Step3)), + MaturityStep4: types.Int64Value(int64(ps.Maturity.Step4)), + MaturityStep5: types.Int64Value(int64(ps.Maturity.Step5)), + Measures: getMeasuresFromMap(ps.Measures), + } + + return psrm, diag } diff --git a/auxo/resource_state.go b/auxo/resource_state.go index a3660e3..7c6675e 100644 --- a/auxo/resource_state.go +++ b/auxo/resource_state.go @@ -3,205 +3,292 @@ package auxo import ( "context" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/on2itsecurity/go-auxo" "github.com/on2itsecurity/go-auxo/zerotrust" - - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func resourceState() *schema.Resource { - return &schema.Resource{ - CreateContext: resourceStateCreate, - ReadContext: resourceStateRead, - UpdateContext: resourceStateUpdate, - DeleteContext: resourceStateDelete, - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - Schema: map[string]*schema.Schema{ - "id": { - Type: schema.TypeString, - Computed: true, - Description: "Unique ID of the resource/state", +var _ resource.Resource = &stateResource{} + +type stateResource struct { + client *auxo.Client +} + +type stateResourceModel struct { + ID types.String `tfsdk:"id"` + Uniqueness_key types.String `tfsdk:"uniqueness_key"` + Description types.String `tfsdk:"description"` + Protectsurface types.String `tfsdk:"protectsurface_id"` + Location types.String `tfsdk:"location_id"` + ContentType types.String `tfsdk:"content_type"` + ExistsOnAssets types.Set `tfsdk:"exists_on_assets"` + Maintainer types.String `tfsdk:"maintainer"` + Content types.Set `tfsdk:"content"` +} + +func NewStateResource() resource.Resource { + return &stateResource{} +} + +func (r *stateResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_state" +} + +func (r *stateResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + // Retrieve the client from the provider config + r.client = req.ProviderData.(*auxo.Client) +} + +func (r *stateResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "A state contains resources and their location, belonging to a protect surface.", + MarkdownDescription: "A state contains resources and their location, belonging to a protect surface.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Computed unique ID of the resource state", + MarkdownDescription: "Computed unique ID of the resource state", + Required: false, + Optional: false, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, - "uniqueness_key": { - Type: schema.TypeString, - Computed: true, - Optional: true, - ForceNew: true, - Description: "Unique key to generate the ID - only needed for parallel import", + "uniqueness_key": schema.StringAttribute{ + Description: "Custom and optinal uniqueness key to identify the resource state", + MarkdownDescription: "Custom and optinal uniqueness key to identify the resource state", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + stringplanmodifier.UseStateForUnknown(), + }, }, - "description": { - Type: schema.TypeString, - Required: true, - Description: "Description of the segment", + "description": schema.StringAttribute{ + Description: "Description of the resource state", + MarkdownDescription: "Description of the resource state", + Required: true, }, - "protectsurface_id": { - Type: schema.TypeString, - Required: true, - Description: "ProtectSurface ID", - ForceNew: true, + "protectsurface_id": schema.StringAttribute{ + Description: "ID of the protect surface", + MarkdownDescription: "ID of the protect surface", + Required: true, }, - "location_id": { - Type: schema.TypeString, - Required: true, - Description: "Location ID", - ForceNew: true, + "location_id": schema.StringAttribute{ + Description: "ID of the location", + MarkdownDescription: "ID of the location", + Required: true, }, - "content_type": { - Type: schema.TypeString, - Default: "ipv4", - Description: "Content type of the state i.e. ipv4, ipv6, azure_cloud", - Optional: true, + "content_type": schema.StringAttribute{ + Description: "Content type of the state i.e. ipv4, ipv6, azure_resource", + MarkdownDescription: "Content type of the state i.e. ipv4, ipv6, azure_resource", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("ipv4"), }, - "exists_on_assets": { - Type: schema.TypeSet, - Optional: true, - Description: "Contains asset IDs which could match this state", - Elem: &schema.Schema{Type: schema.TypeString}, + "exists_on_assets": schema.SetAttribute{ + Description: "Contains asset IDs which could match this state", + MarkdownDescription: "Contains asset IDs which could match this state", + Optional: true, + Computed: true, + ElementType: types.StringType, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.UseStateForUnknown(), + }, }, - "maintainer": { - Type: schema.TypeString, - Optional: true, - Default: "api", - Description: "Maintainer of the state either api or portal_manual", + "maintainer": schema.StringAttribute{ + Description: "Maintainer of the state either api or portal_manual", + MarkdownDescription: "Maintainer of the state either api or portal_manual", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("api_terraform"), }, - "content": { - Type: schema.TypeSet, - Required: true, - Description: "Content of the state e.g. \"10.1.1.2/32\",\"10.1.1.3/32\"", - Elem: &schema.Schema{ - Type: schema.TypeString, - }, + "content": schema.SetAttribute{ + Description: "Content of the state e.g. \"10.1.1.2/32\",\"10.1.1.3/32\"", + MarkdownDescription: "Content of the state e.g. \"10.1.1.2/32\",\"10.1.1.3/32\"", + Required: true, + ElementType: types.StringType, }, }, } } -func resourceStateCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics +func (r *stateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + //Retrieve values from plan + var plan stateResourceModel - provider := m.(*AuxoProvider) - apiClient := provider.APIClient + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) - var state = new(zerotrust.State) + if resp.Diagnostics.HasError() { + return + } - state.UniquenessKey = d.Get("uniqueness_key").(string) - state.Description = d.Get("description").(string) - state.ProtectSurface = d.Get("protectsurface_id").(string) - state.Location = d.Get("location_id").(string) - state.ContentType = d.Get("content_type").(string) - state.Maintainer = d.Get("maintainer").(string) - state.ExistsOnAssetIDs = createStringSliceFromListInput(d.Get("exists_on_assets").(*schema.Set).List()) - content := createStringSliceFromListInput(d.Get("content").(*schema.Set).List()) - state.Content = &content + // Create state (object) + state := resourceModelToState(&plan, ctx) - result, err := apiClient.ZeroTrust.CreateStateByObject(*state) + // Create state (API) + result, err := r.client.ZeroTrust.CreateStateByObject(state) if err != nil { - return diag.FromErr(err) + resp.Diagnostics.AddError("Error creating state", "unexpected error: "+err.Error()) + return } - d.SetId(result.ID) + // Map resonse to schema + plan = stateToResourceModel(result, ctx) - resourceStateRead(ctx, d, m) + // Set state + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) - return diags -} - -func resourceStateRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics - - provider := m.(*AuxoProvider) - apiClient := provider.APIClient + if resp.Diagnostics.HasError() { + return + } - state, err := apiClient.ZeroTrust.GetStateByID(d.Id()) +} +func (r *stateResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var state stateResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + // Get refreshed state from AUXO + result, err := r.client.ZeroTrust.GetStateByID(state.ID.ValueString()) if err != nil { apiError := getAPIError(err) - //NotExists - if apiError.ID == "410" { - d.SetId("") - return nil + if apiError.ID == "410" { // Location not found and probably deleted + resp.State.RemoveResource(ctx) + return + } else { + resp.Diagnostics.AddError("Error reading location", "unexpected error: "+err.Error()) + return } - - return diag.FromErr(err) } - d.Set("id", state.ID) - d.Set("uniqueness_key", state.UniquenessKey) - d.Set("description", state.Description) - d.Set("protectsurface_id", state.ProtectSurface) - d.Set("location_id", state.Location) - d.Set("content_type", state.ContentType) - d.Set("maintainer", state.Maintainer) - d.Set("exists_on_assets", state.ExistsOnAssetIDs) - d.Set("content", state.Content) + //Overwrite state with refreshed state + state = stateToResourceModel(result, ctx) + + //Set refreshed state + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } - return diags } +func (r *stateResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + //Retrieve values from plan + var plan stateResourceModel -func resourceStateUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) - provider := m.(*AuxoProvider) - apiClient := provider.APIClient + if resp.Diagnostics.HasError() { + return + } - state, err := apiClient.ZeroTrust.GetStateByID(d.Id()) + // Create state (object) + state := resourceModelToState(&plan, ctx) + + // Create state (API) + result, err := r.client.ZeroTrust.CreateStateByObject(state) if err != nil { - return diag.FromErr(err) + resp.Diagnostics.AddError("Error creating state", "unexpected error: "+err.Error()) } - if d.HasChange("uniqueness_key") { - state.UniquenessKey = d.Get("uniqueness_key").(string) - } - if d.HasChange("description") { - state.Description = d.Get("description").(string) - } - if d.HasChange("protectsurface_id") { - state.ProtectSurface = d.Get("protectsurface_id").(string) - } - if d.HasChange("location_id") { - state.Location = d.Get("location_id").(string) - } - if d.HasChange("content_type") { - state.ContentType = d.Get("content_type").(string) - } - if d.HasChange("maintainer") { - state.Maintainer = d.Get("maintainer").(string) - } - if d.HasChange("exists_on_assets") { - state.ExistsOnAssetIDs = createStringSliceFromListInput(d.Get("exists_on_assets").(*schema.Set).List()) - } - if d.HasChange("content") { - content := createStringSliceFromListInput(d.Get("content").(*schema.Set).List()) - state.Content = &content + // Map resonse to schema + plan = stateToResourceModel(result, ctx) + + // Set state + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return } - _, err = apiClient.ZeroTrust.UpdateState(*state) +} - if err != nil { - return diag.FromErr(err) +func (r *stateResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // Retrieve values from state + var state stateResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } - return diags + // Delete state + err := r.client.ZeroTrust.DeleteStateByID(state.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Error deleting state", "unexpected error: "+err.Error()) + return + } } -func resourceStateDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - provider := m.(*AuxoProvider) - apiClient := provider.APIClient +// resourceModelToState maps the resource model to the zerotrust.state object +func resourceModelToState(m *stateResourceModel, ctx context.Context) zerotrust.State { + var existsOnAssets, content []string - var diags diag.Diagnostics + if !m.ExistsOnAssets.IsNull() { + _ = m.ExistsOnAssets.ElementsAs(ctx, &existsOnAssets, false) + } - err := apiClient.ZeroTrust.DeleteStateByID(d.Id()) + if !m.Content.IsNull() { + _ = m.Content.ElementsAs(ctx, &content, false) + } - if err != nil { - return diag.FromErr(err) + state := zerotrust.State{ + ID: m.ID.ValueString(), + UniquenessKey: m.Uniqueness_key.ValueString(), + Description: m.Description.ValueString(), + ProtectSurface: m.Protectsurface.ValueString(), + Location: m.Location.ValueString(), + ContentType: m.ContentType.ValueString(), + ExistsOnAssetIDs: existsOnAssets, + Maintainer: m.Maintainer.ValueString(), + Content: &content, } + return state +} + +// StateToResouceModel maps the zerotrust.state object to the resource model +func stateToResourceModel(state *zerotrust.State, ctx context.Context) stateResourceModel { + var existsOnAssets, content basetypes.SetValue - d.SetId("") + if state.ExistsOnAssetIDs != nil { + existsOnAssets, _ = types.SetValueFrom(ctx, types.StringType, state.ExistsOnAssetIDs) + } + if state.Content != nil { + content, _ = types.SetValueFrom(ctx, types.StringType, *state.Content) + } - return diags + return stateResourceModel{ + ID: types.StringValue(state.ID), + Uniqueness_key: types.StringValue(state.UniquenessKey), + Description: types.StringValue(state.Description), + Protectsurface: types.StringValue(state.ProtectSurface), + Location: types.StringValue(state.Location), + ContentType: types.StringValue(state.ContentType), + ExistsOnAssets: existsOnAssets, + Maintainer: types.StringValue(state.Maintainer), + Content: content, + } } diff --git a/auxo/resource_transactionflow.go b/auxo/resource_transactionflow.go index 49ca5bd..986ddb1 100644 --- a/auxo/resource_transactionflow.go +++ b/auxo/resource_transactionflow.go @@ -2,197 +2,318 @@ package auxo import ( "context" + "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/on2itsecurity/go-auxo" "github.com/on2itsecurity/go-auxo/zerotrust" ) -func resourceTransactionFlow() *schema.Resource { - return &schema.Resource{ - CreateContext: resourceTransactionFlowCreate, - ReadContext: resourceTransactionFlowRead, - UpdateContext: resourceTransactionFlowUpdate, - DeleteContext: resourceTransactionFlowDelete, - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - Schema: map[string]*schema.Schema{ - "protectsurface": { - Type: schema.TypeString, - Required: true, - Description: "The corresponding protect surface id", +var _ resource.Resource = &transactionflowResource{} + +type transactionflowResource struct { + client *auxo.Client +} + +type transactionflowResourceModel struct { + Protectsurface types.String `tfsdk:"protectsurface"` + Incoming_protectsurfaces_allow types.Set `tfsdk:"incoming_protectsurfaces_allow"` + Incoming_protectsurfaces_block types.Set `tfsdk:"incoming_protectsurfaces_block"` + Outgoing_protectsurfaces_allow types.Set `tfsdk:"outgoing_protectsurfaces_allow"` + Outgoing_protectsurfaces_block types.Set `tfsdk:"outgoing_protectsurfaces_block"` +} + +type flows struct { + incomingPSAllow []basetypes.StringValue + incomingPSBlock []basetypes.StringValue + outgoingPSAllow []basetypes.StringValue + outgoingPSBlock []basetypes.StringValue +} + +func NewTransactionflowResource() resource.Resource { + return &transactionflowResource{} +} + +func (r *transactionflowResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_transactionflow" +} + +func (r *transactionflowResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + // Retrieve the client from the provider config + r.client = req.ProviderData.(*auxo.Client) +} + +func (r *transactionflowResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "A transactionflow resource represents the defined incoming and outgoing flows on a protect surface.", + MarkdownDescription: "A transactionflow resource represents the defined incoming and outgoing flows on a protect surface.", + Attributes: map[string]schema.Attribute{ + "protectsurface": schema.StringAttribute{ + Description: "The ID of the protectsurface", + MarkdownDescription: "The ID of the protectsurface", + Required: true, }, - "incoming_protectsurfaces_allow": { - Type: schema.TypeSet, - Description: "The corresponding protect surfaces that are allowed to have incoming flows to this protect surface", - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, + "incoming_protectsurfaces_allow": schema.SetAttribute{ + Description: "The IDs of the protectsurface that are allowed to send traffic to this protectsurface", + MarkdownDescription: "The IDs of the protectsurface that are allowed to send traffic to this protectsurface", + Optional: true, + ElementType: types.StringType, }, - "incoming_protectsurfaces_block": { - Type: schema.TypeSet, - Description: "The corresponding protect surfaces that are denied to have incoming flows to this protect surface", - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, + "incoming_protectsurfaces_block": schema.SetAttribute{ + Description: "The IDs of the protectsurface that are blocked to send traffic to this protectsurface", + MarkdownDescription: "The IDs of the protectsurface that are blocked to send traffic to this protectsurface", + Optional: true, + ElementType: types.StringType, }, - "outgoing_protectsurfaces_allow": { - Type: schema.TypeSet, - Description: "Protect surfaces to which flows are allowed (from this protect surface)", - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, + "outgoing_protectsurfaces_allow": schema.SetAttribute{ + Description: "The IDs of the protectsurface that are allowed to send traffic to from this protectsurface", + MarkdownDescription: "The IDs of the protectsurface that are allowed to send traffic to from this protectsurface", + Optional: true, + ElementType: types.StringType, }, - "outgoing_protectsurfaces_block": { - Type: schema.TypeSet, - Description: "Protect surfaces to which flows are denied (from this protect surface)", - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, + "outgoing_protectsurfaces_block": schema.SetAttribute{ + Description: "The IDs of the protectsurface that are blocked to send traffic to from this protectsurface", + MarkdownDescription: "The IDs of the protectsurface that are blocked to send traffic to from this protectsurface", + Optional: true, + ElementType: types.StringType, }, }, } } -func resourceTransactionFlowCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics +func (r *transactionflowResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan transactionflowResourceModel - provider := m.(*AuxoProvider) - apiClient := provider.APIClient + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) - psID := d.Get("protectsurface").(string) - ps, _ := apiClient.ZeroTrust.GetProtectSurfaceByID(psID) + if resp.Diagnostics.HasError() { + return + } - //Flows from other PS - flowsFromOtherPS := make(map[string]zerotrust.Flow) - createFlowsFromSetInput(flowsFromOtherPS, d.Get("incoming_protectsurfaces_allow"), true) - createFlowsFromSetInput(flowsFromOtherPS, d.Get("incoming_protectsurfaces_block"), false) - ps.FlowsFromOtherPS = flowsFromOtherPS + // create transactionflow + psID := plan.Protectsurface.ValueString() + ps, err := r.client.ZeroTrust.GetProtectSurfaceByID(psID) - //Flows to other PS - flowsToOtherPS := make(map[string]zerotrust.Flow) - createFlowsFromSetInput(flowsToOtherPS, d.Get("outgoing_protectsurfaces_allow"), true) - createFlowsFromSetInput(flowsToOtherPS, d.Get("outgoing_protectsurfaces_block"), false) - ps.FlowsToOtherPS = flowsToOtherPS + if err != nil { + resp.Diagnostics.AddError("Error creating transactionflow", "unexpected error: "+err.Error()) + return + } - _, err := apiClient.ZeroTrust.CreateProtectSurfaceByObject(*ps, true) + var f flows + _ = plan.Incoming_protectsurfaces_allow.ElementsAs(ctx, &f.incomingPSAllow, false) + _ = plan.Incoming_protectsurfaces_block.ElementsAs(ctx, &f.incomingPSBlock, false) + _ = plan.Outgoing_protectsurfaces_allow.ElementsAs(ctx, &f.outgoingPSAllow, false) + _ = plan.Outgoing_protectsurfaces_block.ElementsAs(ctx, &f.outgoingPSBlock, false) + ps, err = setFlowsOnPS(ps, f) if err != nil { - return diag.FromErr(err) + resp.Diagnostics.AddError("Error creating transactionflow", err.Error()) + return } - d.SetId(psID) + ps, err = r.client.ZeroTrust.CreateProtectSurfaceByObject(*ps, true) - resourceLocationRead(ctx, d, m) + if err != nil { + resp.Diagnostics.AddError("Error creating transactionflow", "unexpected error: "+err.Error()) + return + } - return diags -} + // Map resonse to schema + plan.Protectsurface = types.StringValue(ps.ID) + f = readFlowsFromPS(ps) + plan.Incoming_protectsurfaces_allow, _ = types.SetValueFrom(ctx, types.StringType, f.incomingPSAllow) + plan.Incoming_protectsurfaces_block, _ = types.SetValueFrom(ctx, types.StringType, f.incomingPSBlock) + plan.Outgoing_protectsurfaces_allow, _ = types.SetValueFrom(ctx, types.StringType, f.outgoingPSAllow) + plan.Outgoing_protectsurfaces_block, _ = types.SetValueFrom(ctx, types.StringType, f.outgoingPSBlock) -func resourceTransactionFlowRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - var diags diag.Diagnostics + // Set state + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) - provider := m.(*AuxoProvider) - apiClient := provider.APIClient + if resp.Diagnostics.HasError() { + return + } +} - ps, err := apiClient.ZeroTrust.GetProtectSurfaceByID(d.Get("protectsurface").(string)) +func (r *transactionflowResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Get current state + var state transactionflowResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + // Get refreshed state from AUXO + result, err := r.client.ZeroTrust.GetProtectSurfaceByID(state.Protectsurface.ValueString()) if err != nil { - apiError := getAPIError(err) - - //NotExists - if apiError.ID == "410" { - d.SetId("") - return nil - } + resp.Diagnostics.AddError("Error reading location", "unexpected error: "+err.Error()) + return + } - return diag.FromErr(err) + //Overwrite state with refreshed state + f := readFlowsFromPS(result) + state.Protectsurface = types.StringValue(result.ID) + state.Incoming_protectsurfaces_allow, _ = types.SetValueFrom(ctx, types.StringType, f.incomingPSAllow) + state.Incoming_protectsurfaces_block, _ = types.SetValueFrom(ctx, types.StringType, f.incomingPSBlock) + state.Outgoing_protectsurfaces_allow, _ = types.SetValueFrom(ctx, types.StringType, f.outgoingPSAllow) + state.Outgoing_protectsurfaces_block, _ = types.SetValueFrom(ctx, types.StringType, f.outgoingPSBlock) + + //Set refreshed state + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } - d.SetId(ps.ID) +} - d.Set("protectsurface", ps.ID) - incomingPSAllow := []string{} - incomingPSBlock := []string{} - outgoingPSAllow := []string{} - outgoingPSBlock := []string{} +func (r *transactionflowResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + //Retrieve values from plan + var plan transactionflowResourceModel - for psID, flow := range ps.FlowsFromOtherPS { - if flow.Allow { - incomingPSAllow = append(incomingPSAllow, psID) - } else { - incomingPSBlock = append(incomingPSBlock, psID) - } - } - for psID, flow := range ps.FlowsToOtherPS { - if flow.Allow { - outgoingPSAllow = append(outgoingPSAllow, psID) - } else { - outgoingPSBlock = append(outgoingPSBlock, psID) - } + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return } - d.Set("incoming_protectsurfaces_allow", incomingPSAllow) - d.Set("incoming_protectsurfaces_block", incomingPSBlock) - d.Set("outgoing_protectsurfaces_allow", outgoingPSAllow) - d.Set("outgoing_protectsurfaces_block", outgoingPSBlock) + // create transactionflow + psID := plan.Protectsurface.ValueString() + ps, err := r.client.ZeroTrust.GetProtectSurfaceByID(psID) - return diags -} + if err != nil { + resp.Diagnostics.AddError("Error creating transactionflow", "unexpected error: "+err.Error()) + return + } -func resourceTransactionFlowUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - provider := m.(*AuxoProvider) - apiClient := provider.APIClient + var f flows + _ = plan.Incoming_protectsurfaces_allow.ElementsAs(ctx, &f.incomingPSAllow, false) + _ = plan.Incoming_protectsurfaces_block.ElementsAs(ctx, &f.incomingPSBlock, false) + _ = plan.Outgoing_protectsurfaces_allow.ElementsAs(ctx, &f.outgoingPSAllow, false) + _ = plan.Outgoing_protectsurfaces_block.ElementsAs(ctx, &f.outgoingPSBlock, false) + + ps, err = setFlowsOnPS(ps, f) + if err != nil { + resp.Diagnostics.AddError("Error creating transactionflow", err.Error()) + return + } - ps, err := apiClient.ZeroTrust.GetProtectSurfaceByID(d.Get("protectsurface").(string)) + ps, err = r.client.ZeroTrust.CreateProtectSurfaceByObject(*ps, true) if err != nil { - return diag.FromErr(err) + resp.Diagnostics.AddError("Error creating transactionflow", "unexpected error: "+err.Error()) + return } - if d.HasChange("incoming_protectsurfaces_allow") || d.HasChange("incoming_protectsurfaces_block") { - flowsFromOtherPS := make(map[string]zerotrust.Flow) - createFlowsFromSetInput(flowsFromOtherPS, d.Get("incoming_protectsurfaces_allow"), true) - createFlowsFromSetInput(flowsFromOtherPS, d.Get("incoming_protectsurfaces_block"), false) - ps.FlowsFromOtherPS = flowsFromOtherPS + // Map resonse to schema + plan.Protectsurface = types.StringValue(ps.ID) + f = readFlowsFromPS(ps) + + plan.Incoming_protectsurfaces_allow, _ = types.SetValueFrom(ctx, types.StringType, f.incomingPSAllow) + plan.Incoming_protectsurfaces_block, _ = types.SetValueFrom(ctx, types.StringType, f.incomingPSBlock) + plan.Outgoing_protectsurfaces_allow, _ = types.SetValueFrom(ctx, types.StringType, f.outgoingPSAllow) + plan.Outgoing_protectsurfaces_block, _ = types.SetValueFrom(ctx, types.StringType, f.outgoingPSBlock) + + // Set state + diags = resp.State.Set(ctx, &plan) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return } - if d.HasChange("outgoing_protectsurfaces_allow") || d.HasChange("outgoing_protectsurfaces_block") { - flowsToOtherPS := make(map[string]zerotrust.Flow) - createFlowsFromSetInput(flowsToOtherPS, d.Get("outgoing_protectsurfaces_allow"), true) - createFlowsFromSetInput(flowsToOtherPS, d.Get("outgoing_protectsurfaces_block"), false) - ps.FlowsToOtherPS = flowsToOtherPS + +} + +func (r *transactionflowResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // Retrieve values from state + var state transactionflowResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } - _, err = apiClient.ZeroTrust.CreateProtectSurfaceByObject(*ps, true) + // Get PS and remove flows + ps, err := r.client.ZeroTrust.GetProtectSurfaceByID(state.Protectsurface.ValueString()) + ps.FlowsFromOtherPS = map[string]zerotrust.Flow{} + ps.FlowsToOtherPS = map[string]zerotrust.Flow{} + + // Update PS, with deleted flows + _, err = r.client.ZeroTrust.CreateProtectSurfaceByObject(*ps, true) if err != nil { - return diag.FromErr(err) + resp.Diagnostics.AddError("Error deleting state", "unexpected error: "+err.Error()) + return } +} + +// readFlowsFromPS, get a ProtectSurface and return a flows struct, which can be used to map directly on plan & state +func readFlowsFromPS(ps *zerotrust.ProtectSurface) flows { + var f flows - return resourceProtectSurfaceRead(ctx, d, m) + for psID, flow := range ps.FlowsFromOtherPS { + if flow.Allow { + f.incomingPSAllow = append(f.incomingPSAllow, basetypes.NewStringValue(psID)) + } else { + f.incomingPSBlock = append(f.incomingPSBlock, basetypes.NewStringValue(psID)) + } + } + + for psID, flow := range ps.FlowsToOtherPS { + if flow.Allow { + f.outgoingPSAllow = append(f.outgoingPSAllow, basetypes.NewStringValue(psID)) + } else { + f.outgoingPSBlock = append(f.outgoingPSBlock, basetypes.NewStringValue(psID)) + } + } + + return f } -func resourceTransactionFlowDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { - provider := m.(*AuxoProvider) - apiClient := provider.APIClient +// setFlowsOnPS, get a ProtectSurface and a flows struct, and set the flows on the ProtectSurface +func setFlowsOnPS(ps *zerotrust.ProtectSurface, flows flows) (*zerotrust.ProtectSurface, error) { - var diags diag.Diagnostics + //Check for duplicates in incoming and outgoing, which is not allowed + for _, flow := range flows.incomingPSAllow { + if sliceContains(getSliceFromSetOfString(flows.incomingPSBlock), flow.ValueString()) { + return nil, fmt.Errorf("duplicate in incoming_protectsurfaces_allow and incoming_protectsurfaces_block, protectsurface ID: %s", flow.ValueString()) + } + } - ps, err := apiClient.ZeroTrust.GetProtectSurfaceByID(d.Get("protectsurface").(string)) + for _, flow := range flows.outgoingPSAllow { + if sliceContains(getSliceFromSetOfString(flows.outgoingPSBlock), flow.ValueString()) { + return nil, fmt.Errorf("duplicate in outgoing_protectsurfaces_allow and outgoing_protectsurfaces_block, protectsurface ID: %s", flow.ValueString()) + } + } ps.FlowsFromOtherPS = map[string]zerotrust.Flow{} ps.FlowsToOtherPS = map[string]zerotrust.Flow{} - _, err = apiClient.ZeroTrust.CreateProtectSurfaceByObject(*ps, true) - - if err != nil { - return diag.FromErr(err) + for _, flow := range flows.incomingPSAllow { + ps.FlowsFromOtherPS[flow.ValueString()] = zerotrust.Flow{Allow: true} } - d.SetId("") + for _, flow := range flows.incomingPSBlock { + ps.FlowsFromOtherPS[flow.ValueString()] = zerotrust.Flow{Allow: false} + } - return diags -} + for _, flow := range flows.outgoingPSAllow { + ps.FlowsToOtherPS[flow.ValueString()] = zerotrust.Flow{Allow: true} + } -// createFlowsFromSetInput fills a map of flows from a set input -func createFlowsFromSetInput(flows map[string]zerotrust.Flow, inputField interface{}, allow bool) { - for _, psID := range createStringSliceFromListInput(inputField.(*schema.Set).List()) { - flows[psID] = zerotrust.Flow{Allow: allow} + for _, flow := range flows.outgoingPSBlock { + ps.FlowsToOtherPS[flow.ValueString()] = zerotrust.Flow{Allow: false} } + + return ps, nil } diff --git a/auxo/utils.go b/auxo/utils.go index ea30951..929451c 100644 --- a/auxo/utils.go +++ b/auxo/utils.go @@ -3,24 +3,17 @@ package auxo import ( "encoding/json" "strings" + + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) +// apiError is the struct for the error returned by the go-auxo API type apiError struct { ID string `json:"error_id"` Name string `json:"error_name"` Message string `json:"error_message"` } -// createStringSliceFromListInput converts a slice/list of interface{} to a slice of strings -func createStringSliceFromListInput(inputList []interface{}) []string { - output := make([]string, len(inputList)) - for k, v := range inputList { - output[k] = v.(string) - } - - return output -} - // getAPIError returns an apiError struct from a go-auxo error func getAPIError(err error) apiError { var apiErr apiError @@ -30,6 +23,15 @@ func getAPIError(err error) apiError { return apiErr } +// getSliceFromSetOfString converts a slice of basetypes.StringValue to a slice of string +func getSliceFromSetOfString(values []basetypes.StringValue) []string { + result := []string{} + for _, value := range values { + result = append(result, value.ValueString()) + } + return result +} + // sliceContains checks if a string is in a slice of strings func sliceContains(slice []string, match string) bool { for _, str := range slice { diff --git a/docs/data-sources/contact.md b/docs/data-sources/contact.md index 1a16c46..9340ffc 100644 --- a/docs/data-sources/contact.md +++ b/docs/data-sources/contact.md @@ -1,19 +1,18 @@ --- -# generated by https://github.com/hashicorp/terraform-plugin-docs page_title: "auxo_contact Data Source - terraform-provider-auxo" subcategory: "" description: |- - + Auxo Contact --- -# Data `contact` +# auxo_contact (Data Source) -A contact which can be used as main- or securitycontact in a `protectsurface`. +Auxo Contact ## Example Usage -```hcl -data "auxo_contact" "rob"{ +```terraform +data "auxo_contact" "rob" { email = "rob.maas+tst@on2it.net" } ``` @@ -23,8 +22,8 @@ data "auxo_contact" "rob"{ ### Required -- `email` (String) +- `email` (String) Emails of the contact ### Read-Only -- `id` (String) The ID of this resource. \ No newline at end of file +- `id` (String) Computed unique IDs of the contact diff --git a/docs/index.md b/docs/index.md index 690426c..1eafe0c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,5 @@ --- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "Provider: AUXO" +page_title: Provider: AUXO subcategory: "" description: |- Terraform provider for interacting with AUXO. @@ -8,37 +7,78 @@ description: |- # AUXO Provider -This provider is build to interact with the AUXO platform. +This provider is build to interact with the [ON2IT AUXO Zero Trust Platform](https://on2it.net/managed-security/#section-auxo). + +When upgrading to version 0.0.7, the following changes might break your configuration. + +- Environment variable AUXOTOKEN is now AUXO_TOKEN +- Measure format within the `auxo_protectsurface` resource has changed. ## Example Usage -```hcl +```terraform terraform { required_providers { auxo = { source = "on2itsecurity/auxo" - version = "0.0.3" + version = "0.0.6" } } } ``` -Provide your AUXO token either with setting an environment variable `AUXOTOKEN` (recommended) or by setting it in the provider configuration. +Provide your AUXO token either with setting an environment variable `AUXO_TOKEN` (recommended) or by setting it in the provider configuration (not recommended). ```shell -export AUXOTOKEN= +export AUXO_TOKEN= +``` + +### Example with token + +```terraform +provider "auxo" { + token = "VerySensitiveToken" +} ``` -```hcl +### Example with config name + +```terraform provider "auxo" { - token = "" + name = "tenant2" } ``` +By default it will look for the configuration in `~/.ztctl/config.json`, which has the following format. This location can be overridden by setting the `config` attribute. + +The `name` configuration attribute will take precedence over the `token` and `url` attributes. + +```json +{ + "configs": [ + { + "alias": "tenant1", + "description": "This is the token for tenant 1", + "token": "VerySecureTokenTenant1", + "apiaddress": "api.on2it.dev", + "debug": false + }, + { + "alias": "tenant2", + "description": "This is the token for tenant 2", + "token": "VerySecureTokenTenant2", + "apiaddress": "api.on2it.net", + "debug": false + } + ] +} + ## Schema ### Optional -- `token` (String, Sensitive) - The token to use for authentication. -- `url` (String) - The URL of the AUXO API. +- `config` (String) Location of the ztctl configuration file, will default to `~/.ztctl/config.json` +- `name` (String) The alias in the ztctl configuration file, this takes precedence over the url and token attributes +- `token` (String, Sensitive) The token to access the API +- `url` (String) The URL of the Auxo API \ No newline at end of file diff --git a/docs/resources/location.md b/docs/resources/location.md index 7bc590a..e982e0d 100644 --- a/docs/resources/location.md +++ b/docs/resources/location.md @@ -1,18 +1,17 @@ --- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "location Resource - terraform-provider-auxo" +page_title: "auxo_location Resource - terraform-provider-auxo" subcategory: "" description: |- A location which can be used in a state to reflect where resources are located. --- -# Resource `location` +# auxo_location (Resource) A location which can be used in a state to reflect where resources are located. ## Example Usage -```hcl +```terraform resource "auxo_location" "loc_zaltbommel" { name = "Datacenter Zaltbommel" latitude = 51.7983645 @@ -25,16 +24,14 @@ resource "auxo_location" "loc_zaltbommel" { ### Required -- `name` (String) Unique name of the location +- `name` (String) Name of the resource location ### Optional -- `latitude` (Number) Latitude of the location -- `longitude` (Number) Longitude of the location -- `uniqueness_key` (String) Unique key to generate the ID - only needed for parallel import +- `latitude` (Number) Latitude of the resource location +- `longitude` (Number) Longitude of the resource location +- `uniqueness_key` (String) Custom and optinal uniqueness key to identify the resource location ### Read-Only -- `id` (String) Unique ID of the resource/location - - +- `id` (String) Computed unique ID of the resource location diff --git a/docs/resources/protectsurface.md b/docs/resources/protectsurface.md index fcfde17..25a7ac1 100644 --- a/docs/resources/protectsurface.md +++ b/docs/resources/protectsurface.md @@ -1,18 +1,18 @@ --- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "protectsurface Resource - terraform-provider-auxo" +page_title: "auxo_protectsurface Resource - terraform-provider-auxo" subcategory: "" description: |- A zero trust protectsurface which reflects what you want to protect. --- -# Resource `protectsurface` +# auxo_protectsurface (Resource) A zero trust protectsurface which reflects what you want to protect. ## Example Usage -```hcl +```terraform +// Represents protect-surface "Active Directory" resource "auxo_protectsurface" "ps_ad" { name = "Active Directory" description = "Active Directory for employees" @@ -22,8 +22,8 @@ resource "auxo_protectsurface" "ps_ad" { confidentiality = 3 availability = 2 integrity = 3 - main_contact = "37904" - security_contact = "37904" + main_contact = data.auxo_contact.rob.id + security_contact = data.auxo_contact.rob.id data_tags = ["PII"] compliance_tags = ["GDPR"] customer_labels = { @@ -32,72 +32,97 @@ resource "auxo_protectsurface" "ps_ad" { os = "Windows" created-by = "Terraform" } - soc_tags = ["active-directory", "windows"] - allow_flows_from_outside = false - allow_flows_to_outside = false + allow_flows_from_outside = false + allow_flows_to_outside = false } ``` +### Example uses of measures + +```terraform +measures = { + flows-segmentation = { + assigned = true + assigned_by = data.auxo_contact.rob.email + implemented = true + implemented_by = data.auxo_contact.rob.email + evidenced = false + evidenced_by = data.auxo_contact.rob.email + }, + encryption-at-rest = { + assigned = true + assigned_by = data.auxo_contact.rob.email + implemented = true + implemented_by = data.auxo_contact.rob.email + evidenced = false + evidenced_by = data.auxo_contact.rob.email + }, + encryption-in-transit = { + assigned = true + assigned_by = data.auxo_contact.rob.email + implemented = true + implemented_by = data.auxo_contact.rob.email + evidenced = false + evidenced_by = data.auxo_contact.rob.email + } + } +``` + ## Schema ### Required -- `name` (String) Name of the segment +- `name` (String) Name of the resource protectsurface +- `relevance` (Number) Relevance of the resource protectsurface ### Optional -- `allow_flows_from_outside` (Boolean) Does this protect surface allows to have flows from outside (e.g. Internet) -- `allow_flows_to_outside` (Boolean) Does this protect surface allows to have flows to outside (e.g. Internet) -- `availability` (Number) Availability score (number 1-5) -- `compliance_tags` (Set of String) Contains compliance tags, defining compliancy requirements of the protect surface -- `confidentiality` (Number) Confidentiality score (number 1-5) -- `customer_labels` (Map of String) Contains customer labels in Key-Value-Pair format -- `data_tags` (Set of String) Contains data tags, defining the data residing in the protect surface -- `description` (String) Description of the segment -- `in_control_boundary` (Boolean) Is this protect surface in the control boundary (your responsibility) -- `in_zero_trust_focus` (Boolean) Is this protect surface in the zero trust focus (actively maintained and monitored) -- `integrity` (Number) Integrity score (number 1-5) -- `main_contact` (String) ID of main contact in text) -- `maturity_step1` (Number) maturity step 1 - defining the protect surface -- `maturity_step2` (Number) maturity step 2 - map the transaction flows -- `maturity_step3` (Number) maturity step 3 - architect your environment -- `maturity_step4` (Number) maturity step 4 - zero trust policy -- `maturity_step5` (Number) maturity step 5 - monitor and maintain -- `measure` (Block Set) List of measures set for this protect surface (see [below for nested schema](#nestedblock--measure)) -- `relevance` (Number) Relevance 0-100 of the segment -- `security_contact` (String) ID of security contact in text -- `soc_tags` (Set of String) Contains tags, which are used by the SOC engineers -- `uniqueness_key` (String) Unique key to generate the ID - only needed for parallel import +- `allow_flows_from_outside` (Boolean) Allow flows from outside of the protectsurface coming in +- `allow_flows_to_outside` (Boolean) Allow flows to go outside of the protectsurface +- `availability` (Number) Availability of the resource protectsurface +- `compliance_tags` (Set of String) Compliance tags of the resource protectsurface +- `confidentiality` (Number) Confidentiality of the resource protectsurface +- `customer_labels` (Map of String) Customer labels of the resource protectsurface +- `data_tags` (Set of String) Data tags of the resource protectsurface +- `description` (String) Description of the resource protectsurface +- `in_control_boundary` (Boolean) This protect surface is within the 'control boundary' +- `in_zero_trust_focus` (Boolean) This protect surface is within the 'zero trust focus' (actively maintained and monitored) +- `integrity` (Number) Integrity of the resource protectsurface +- `main_contact` (String) Main contact of the resource protectsurface +- `maturity_step1` (Number) Maturity step 1 +- `maturity_step2` (Number) Maturity step 2 +- `maturity_step3` (Number) Maturity step 3 +- `maturity_step4` (Number) Maturity step 4 +- `maturity_step5` (Number) Maturity step 5 +- `measures` (Attributes Map) Measures of the resource protectsurface (see [below for nested schema](#nestedatt--measures)) +- `security_contact` (String) Security contact of the resource protectsurface +- `soc_tags` (Set of String) SOC tags of the resource protectsurface, only use when advised by the SOC +- `uniqueness_key` (String) Custom and optinal uniqueness key to identify the resource protectsurface ### Read-Only -- `id` (String) Unique ID of the resource/segment +- `id` (String) Computed unique ID of the resource protectsurface - -### Nested Schema for `measure` + +### Nested Schema for `measures` -Contains the assigned, implemented and/or evidenced measure(s) status for this protect surface. +Required: -#### Example Usage - -```hcl - measure { - type = "flows-segmentation" - assigned = true - assigned_by = data.auxo_contact.rob.email - implemented = false - implemented_by = data.auxo_contact.rob.email - evidenced = false - evidenced_by = data.auxo_contact.rob.email - } -``` +- `assigned` (Boolean) Measure assigned to the protectsurface -#### Required: +Optional: -- `type` (String) type of the measure +- `assigned_by` (String) Who assigned this measure to the protectsurface +- `assigned_timestamp` (Number) When was this measure assigned to the protectsurface +- `evidenced` (Boolean) Is there evidence that this measure is implemented +- `evidenced_by` (String) Who evidenced that this measure is implementd +- `evidenced_timestamp` (Number) When was this measure evidenced +- `implemented` (Boolean) Is this measure implemented to the protectsurface +- `implemented_by` (String) Who implemented this measure to the protectsurface +- `implemented_timestamp` (Number) When was this measure implemented to the protectsurface -Current supported measures (`type`): +### Default measures - flows-segmentation - flows-restrict-outbound @@ -132,12 +157,3 @@ Current supported measures (`type`): - data-phishing - data-dlp - data-classification - -#### Optional: - -- `assigned` (Boolean) Is this measure assigned to the protect surface -- `assigned_by` (String) Who assigned this measure to the protect surface -- `evidenced` (Boolean) Is there evidence that this measure is implemented -- `evidenced_by` (String) Who evidenced that this measure is implementd -- `implemented` (Boolean) Is this measure implemented to the protect surface -- `implemented_by` (String) Who implemented this measure to the protect surface diff --git a/docs/resources/state.md b/docs/resources/state.md index c47276c..857a5e1 100644 --- a/docs/resources/state.md +++ b/docs/resources/state.md @@ -1,18 +1,17 @@ --- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "state Resource - terraform-provider-auxo" +page_title: "auxo_state Resource - terraform-provider-auxo" subcategory: "" description: |- - A state contains resources and there location and belonging protect surface. + A state contains resources and their location, belonging to a protect surface. --- -# Resource `state` +# auxo_state (Resource) -A state contains resources and there location and belonging protect surface. +A state contains resources and their location, belonging to a protect surface. ## Example Usage -```hcl +```terraform resource "auxo_state" "ps_ad-loc_zaltbommel-ipv4" { content_type = "ipv4" description = "IPv4 allocations of AD servers" @@ -22,38 +21,3 @@ resource "auxo_state" "ps_ad-loc_zaltbommel-ipv4" { } ``` - -## Schema - -### Required - -- `content` (Set of String) Content of the state e.g. "10.1.1.2/32","10.1.1.3/32" -- `description` (String) Description of the segment -- `location_id` (String) Location ID -- `protectsurface_id` (String) ProtectSurface ID - -### Optional - -- `content_type` (String) Content type of the state i.e. ipv4, ipv6, azure_cloud -- `exists_on_assets` (Set of String) Contains asset IDs which could match this state -- `maintainer` (String) Maintainer of the state either api or portal_manual -- `uniqueness_key` (String) Unique key to generate the ID - only needed for parallel import - -### Read-Only - -- `id` (String) Unique ID of the resource/state - -### Content-Types - -| content type | description | -| ------------- | ----------------------------------------------------- | -| azure_cloud | Contains Azure cloud resource IDs | -| aws_cloud | Contains AWS cloud resource IDs | -| gcp_cloud | Contains GCP cloud resource IDs | -| container | Contains container IDs | -| hostname | Contains hostnames | -| user_identity | Contains user identities; i.e. username and/or e-mail | -| ipv4 | IPv4 address or CIDR i.e. `10.1.2.0/24` | -| ipv6 | IPv6 address or CIDR i.e. `2a02:fe9:692:2812/64` | - - diff --git a/docs/resources/transactionflow.md b/docs/resources/transactionflow.md index 57f49b7..410bc37 100644 --- a/docs/resources/transactionflow.md +++ b/docs/resources/transactionflow.md @@ -1,18 +1,17 @@ --- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "transactionflow Resource - terraform-provider-auxo" +page_title: "auxo_transactionflow Resource - terraform-provider-auxo" subcategory: "" description: |- A transactionflow resource represents the defined incoming and outgoing flows on a protect surface. --- -# Resource `transactionflow` +# auxo_transactionflow (Resource) A transactionflow resource represents the defined incoming and outgoing flows on a protect surface. ## Example Usage -```hcl +```terraform resource "auxo_transactionflow" "tf_ps_ad" { protectsurface = auxo_protectsurface.ps_ad.id incoming_protectsurfaces_allow = [auxo_protectsurface.ps_mail.id] @@ -24,7 +23,7 @@ resource "auxo_transactionflow" "tf_ps_ad" { **Important** if a flow is allowed on protect surface A to go to protect surface B, it does not mean that the flow is accepted on protect surface B. There needs to be mutual consensus, which means two resources of transactionflow are needed, one for each protect surface, see example below. -```hcl +```terraform resource "auxo_transactionflow" "tf_ps_a" { protectsurface = auxo_protectsurface.ps_a.id outgoing_protectsurfaces_allow = [auxo_protectsurface.ps_b.id] @@ -36,22 +35,3 @@ resource "auxo_transactionflow" "tf_ps_b" { } ``` - -## Schema - -### Required - -- `protectsurface` (String) The corresponding protect surface id - -### Optional - -- `incoming_protectsurfaces_allow` (Set of String) The corresponding protect surfaces that are allowed to have incoming flows to this protect surface -- `incoming_protectsurfaces_block` (Set of String) The corresponding protect surfaces that are denied to have incoming flows to this protect surface -- `outgoing_protectsurfaces_allow` (Set of String) Protect surfaces to which flows are allowed (from this protect surface) -- `outgoing_protectsurfaces_block` (Set of String) Protect surfaces to which flows are denied (from this protect surface) - -### Read-Only - -- `id` (String) The ID of this resource. - - diff --git a/examples/data-sources/contact.tf b/examples/data-sources/contact.tf new file mode 100644 index 0000000..76cdfb4 --- /dev/null +++ b/examples/data-sources/contact.tf @@ -0,0 +1,3 @@ +data "auxo_contact" "rob" { + email = "rob.maas+tst@on2it.net" +} \ No newline at end of file diff --git a/example/main.tf b/examples/full-example/main.tf similarity index 91% rename from example/main.tf rename to examples/full-example/main.tf index 5eb4393..449817a 100644 --- a/example/main.tf +++ b/examples/full-example/main.tf @@ -7,6 +7,10 @@ terraform { } } +provider "auxo" { + name = "tailspin" +} + // Get the contact based on the email address data "auxo_contact" "rob" { email = "rob.maas+tst@on2it.net" @@ -37,14 +41,15 @@ resource "auxo_protectsurface" "ps_ad" { allow_flows_to_outside = false // Represents the segmentation measure for this protect-surface - measure { - type = "flows-segmentation" - assigned = true - assigned_by = data.auxo_contact.rob.email - implemented = true - implemented_by = data.auxo_contact.rob.email - evidenced = false - evidenced_by = data.auxo_contact.rob.email + measures = { + flows-segmentation = { + assigned = true + assigned_by = data.auxo_contact.rob.email + implemented = true + implemented_by = data.auxo_contact.rob.email + evidenced = false + evidenced_by = data.auxo_contact.rob.email + } } } @@ -119,7 +124,7 @@ resource "auxo_location" "loc_zaltbommel" { // Represents a state, this can be seen as the glue between protect-surface, location and the type of resources it represent // A protect surface can have multipe states attached to it resource "auxo_state" "ps_ad-loc_beunigen-ipv4" { - content_type = "static_ipv4" + content_type = "ipv4" description = "Static IPv4 allocations of AD servers" location_id = auxo_location.loc_beuningen.id protectsurface_id = auxo_protectsurface.ps_ad.id @@ -127,7 +132,7 @@ resource "auxo_state" "ps_ad-loc_beunigen-ipv4" { } resource "auxo_state" "ps_ad-loc_zaltbommel-ipv4" { - content_type = "static_ipv4" + content_type = "ipv4" description = "Static IPv4 allocations of AD servers" location_id = auxo_location.loc_zaltbommel.id protectsurface_id = auxo_protectsurface.ps_ad.id @@ -135,7 +140,7 @@ resource "auxo_state" "ps_ad-loc_zaltbommel-ipv4" { } resource "auxo_state" "ps_guests-loc_zaltbommel-ipv4" { - content_type = "static_ipv4" + content_type = "ipv4" description = "Static IPv4 subnet for guests" location_id = auxo_location.loc_zaltbommel.id protectsurface_id = auxo_protectsurface.ps_guests.id diff --git a/examples/provider/provider.tf b/examples/provider/provider.tf new file mode 100644 index 0000000..0a6f613 --- /dev/null +++ b/examples/provider/provider.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + auxo = { + source = "on2itsecurity/auxo" + version = "0.0.6" + } + } +} diff --git a/examples/provider/provider_config.tf b/examples/provider/provider_config.tf new file mode 100644 index 0000000..0b2f04b --- /dev/null +++ b/examples/provider/provider_config.tf @@ -0,0 +1,3 @@ +provider "auxo" { + name = "tenant2" +} diff --git a/examples/provider/provider_token.tf b/examples/provider/provider_token.tf new file mode 100644 index 0000000..7eb66bb --- /dev/null +++ b/examples/provider/provider_token.tf @@ -0,0 +1,3 @@ +provider "auxo" { + token = "VerySensitiveToken" +} diff --git a/examples/resources/location.tf b/examples/resources/location.tf new file mode 100644 index 0000000..5d1bb1f --- /dev/null +++ b/examples/resources/location.tf @@ -0,0 +1,5 @@ +resource "auxo_location" "loc_zaltbommel" { + name = "Datacenter Zaltbommel" + latitude = 51.7983645 + longitude = 5.2548381 +} \ No newline at end of file diff --git a/examples/resources/protectsurface-measures.tf b/examples/resources/protectsurface-measures.tf new file mode 100644 index 0000000..982e52c --- /dev/null +++ b/examples/resources/protectsurface-measures.tf @@ -0,0 +1,27 @@ + measures = { + flows-segmentation = { + assigned = true + assigned_by = data.auxo_contact.rob.email + implemented = true + implemented_by = data.auxo_contact.rob.email + evidenced = false + evidenced_by = data.auxo_contact.rob.email + }, + encryption-at-rest = { + assigned = true + assigned_by = data.auxo_contact.rob.email + implemented = true + implemented_by = data.auxo_contact.rob.email + evidenced = false + evidenced_by = data.auxo_contact.rob.email + }, + encryption-in-transit = { + assigned = true + assigned_by = data.auxo_contact.rob.email + implemented = true + implemented_by = data.auxo_contact.rob.email + evidenced = false + evidenced_by = data.auxo_contact.rob.email + } + } + diff --git a/examples/resources/protectsurface.tf b/examples/resources/protectsurface.tf new file mode 100644 index 0000000..ab8fa10 --- /dev/null +++ b/examples/resources/protectsurface.tf @@ -0,0 +1,23 @@ +// Represents protect-surface "Active Directory" +resource "auxo_protectsurface" "ps_ad" { + name = "Active Directory" + description = "Active Directory for employees" + relevance = 90 + in_control_boundary = true + in_zero_trust_focus = true + confidentiality = 3 + availability = 2 + integrity = 3 + main_contact = data.auxo_contact.rob.id + security_contact = data.auxo_contact.rob.id + data_tags = ["PII"] + compliance_tags = ["GDPR"] + customer_labels = { + owner = "Rob Maas" + env = "Production" + os = "Windows" + created-by = "Terraform" + } + allow_flows_from_outside = false + allow_flows_to_outside = false +} diff --git a/examples/resources/state.tf b/examples/resources/state.tf new file mode 100644 index 0000000..f212ad9 --- /dev/null +++ b/examples/resources/state.tf @@ -0,0 +1,7 @@ +resource "auxo_state" "ps_ad-loc_zaltbommel-ipv4" { + content_type = "ipv4" + description = "IPv4 allocations of AD servers" + location_id = auxo_location.loc_zaltbommel.id + protectsurface_id = auxo_protectsurface.ps_ad.id + content = ["10.0.42.10", "10.0.42.11", "10.0.42.12"] +} diff --git a/examples/resources/transactionflow-bidirectional.tf b/examples/resources/transactionflow-bidirectional.tf new file mode 100644 index 0000000..6041112 --- /dev/null +++ b/examples/resources/transactionflow-bidirectional.tf @@ -0,0 +1,9 @@ +resource "auxo_transactionflow" "tf_ps_a" { + protectsurface = auxo_protectsurface.ps_a.id + outgoing_protectsurfaces_allow = [auxo_protectsurface.ps_b.id] +} + +resource "auxo_transactionflow" "tf_ps_b" { + protectsurface = auxo_protectsurface.ps_b.id + incoming_protectsurfaces_allow = [auxo_protectsurface.ps_a.id] +} \ No newline at end of file diff --git a/examples/resources/transactionflow.tf b/examples/resources/transactionflow.tf new file mode 100644 index 0000000..fca4e9b --- /dev/null +++ b/examples/resources/transactionflow.tf @@ -0,0 +1,7 @@ +resource "auxo_transactionflow" "tf_ps_ad" { + protectsurface = auxo_protectsurface.ps_ad.id + incoming_protectsurfaces_allow = [auxo_protectsurface.ps_mail.id] + incoming_protectsurfaces_block = [auxo_protectsurface.ps_guests.id] + outgoing_protectsurfaces_allow = [auxo_protectsurface.ps_mail.id] + outgoing_protectsurfaces_block = [auxo_protectsurface.ps_guests.id] +} \ No newline at end of file diff --git a/go.mod b/go.mod index e9c0b3d..f5cceee 100644 --- a/go.mod +++ b/go.mod @@ -1,51 +1,37 @@ module github.com/on2itsecurity/terraform-provider-auxo -go 1.19 +go 1.20 -require github.com/on2itsecurity/go-auxo v1.0.3 +require ( + github.com/hashicorp/terraform-plugin-framework v1.2.0 + github.com/mitchellh/go-homedir v1.1.0 + github.com/on2itsecurity/go-auxo v1.0.5 +) require ( - github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/go-cmp v0.5.9 // indirect - github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect - github.com/hashicorp/logutils v1.0.0 // indirect - github.com/hashicorp/terraform-plugin-go v0.14.3 // indirect + github.com/hashicorp/terraform-plugin-go v0.15.0 // indirect github.com/hashicorp/terraform-plugin-log v0.8.0 // indirect - github.com/hashicorp/terraform-registry-address v0.1.0 // indirect + github.com/hashicorp/terraform-registry-address v0.2.0 // indirect github.com/hashicorp/terraform-svchost v0.1.0 // indirect - github.com/kr/pretty v0.3.0 // indirect - github.com/mattn/go-isatty v0.0.18 // indirect - github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect - github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect - github.com/vmihailenco/tagparser v0.1.2 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect google.golang.org/protobuf v1.30.0 // indirect ) require ( - github.com/agext/levenshtein v1.2.3 // indirect github.com/fatih/color v1.15.0 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.4.9 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect - github.com/hashicorp/hcl/v2 v2.16.2 // indirect - github.com/hashicorp/terraform-plugin-sdk/v2 v2.26.1 github.com/hashicorp/yamux v0.1.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect - github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/oklog/run v1.1.0 // indirect - github.com/zclconf/go-cty v1.13.1 // indirect - golang.org/x/net v0.8.0 // indirect - golang.org/x/sys v0.6.0 // indirect - golang.org/x/text v0.8.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230320184635-7606e756e683 // indirect - google.golang.org/grpc v1.54.0 // indirect + golang.org/x/net v0.10.0 // indirect + golang.org/x/sys v0.8.0 // indirect + golang.org/x/text v0.9.0 // indirect + google.golang.org/genproto v0.0.0-20230524185152-1884fd1fac28 // indirect + google.golang.org/grpc v1.55.0 // indirect ) diff --git a/go.sum b/go.sum index 6abf41f..ab1b497 100644 --- a/go.sum +++ b/go.sum @@ -1,73 +1,33 @@ -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= -github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= -github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= -github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= -github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= -github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= -github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= -github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.4.9 h1:ESiK220/qE0aGxWdzKIvRH69iLiuN/PjoLTm69RoWtU= github.com/hashicorp/go-plugin v1.4.9/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hcl/v2 v2.16.2 h1:mpkHZh/Tv+xet3sy3F9Ld4FyI2tUpWe9x3XtPx9f1a0= -github.com/hashicorp/hcl/v2 v2.16.2/go.mod h1:JRmR89jycNkrrqnMmvPDMd56n1rQJ2Q6KocSLCMCXng= -github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/terraform-plugin-go v0.14.3 h1:nlnJ1GXKdMwsC8g1Nh05tK2wsC3+3BL/DBBxFEki+j0= -github.com/hashicorp/terraform-plugin-go v0.14.3/go.mod h1:7ees7DMZ263q8wQ6E4RdIdR6nHHJtrdt4ogX5lPkX1A= +github.com/hashicorp/terraform-plugin-framework v1.2.0 h1:MZjFFfULnFq8fh04FqrKPcJ/nGpHOvX4buIygT3MSNY= +github.com/hashicorp/terraform-plugin-framework v1.2.0/go.mod h1:nToI62JylqXDq84weLJ/U3umUsBhZAaTmU0HXIVUOcw= +github.com/hashicorp/terraform-plugin-go v0.15.0 h1:1BJNSUFs09DS8h/XNyJNJaeusQuWc/T9V99ylU9Zwp0= +github.com/hashicorp/terraform-plugin-go v0.15.0/go.mod h1:tk9E3/Zx4RlF/9FdGAhwxHExqIHHldqiQGt20G6g+nQ= github.com/hashicorp/terraform-plugin-log v0.8.0 h1:pX2VQ/TGKu+UU1rCay0OlzosNKe4Nz1pepLXj95oyy0= github.com/hashicorp/terraform-plugin-log v0.8.0/go.mod h1:1myFrhVsBLeylQzYYEV17VVjtG8oYPRFdaZs7xdW2xs= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.26.1 h1:G9WAfb8LHeCxu7Ae8nc1agZlQOSCUWsb610iAogBhCs= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.26.1/go.mod h1:xcOSYlRVdPLmDUoqPhO9fiO/YCN/l6MGYeTzGt5jgkQ= -github.com/hashicorp/terraform-registry-address v0.1.0 h1:W6JkV9wbum+m516rCl5/NjKxCyTVaaUBbzYcMzBDO3U= -github.com/hashicorp/terraform-registry-address v0.1.0/go.mod h1:EnyO2jYO6j29DTHbJcm00E5nQTFeTtyZH3H5ycydQ5A= -github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= +github.com/hashicorp/terraform-registry-address v0.2.0 h1:92LUg03NhfgZv44zpNTLBGIbiyTokQCDcdH5BhVHT3s= +github.com/hashicorp/terraform-registry-address v0.2.0/go.mod h1:478wuzJPzdmqT6OGbB/iH82EDcI8VFM4yujknh/1nIs= github.com/hashicorp/terraform-svchost v0.1.0 h1:0+RcgZdZYNd81Vw7tu62g9JiLLvbOigp7QtyNh6CjXk= github.com/hashicorp/terraform-svchost v0.1.0/go.mod h1:ut8JaH0vumgdCfJaihdcZULqkAwHdQNwNH7taIDdsZM= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -75,88 +35,49 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= -github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= -github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= -github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= -github.com/on2itsecurity/go-auxo v1.0.3 h1:qkRcSv7urx2sU0ATbzcCAXAlib7W02pIIW7F2LZjXL0= -github.com/on2itsecurity/go-auxo v1.0.3/go.mod h1:2ri6DDtR8QR58EoGzbBuz7P+ZpwhjZRww898B27KCVI= +github.com/on2itsecurity/go-auxo v1.0.5 h1:+BI0n/kUZTyX/uxSSuR09obWtFQAIUaZ3rn62a7tujU= +github.com/on2itsecurity/go-auxo v1.0.5/go.mod h1:wIaSHp+k/8ckttQn75hp4DlqUbnoV85Ifk6RoGQIB6I= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= -github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= -github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= -github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U= -github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= -github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= -github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc= -github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= -github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= -github.com/zclconf/go-cty v1.13.1 h1:0a6bRwuiSHtAmqCqNOE+c2oHgepv0ctoxU4FUe43kwc= -github.com/zclconf/go-cty v1.13.1/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20230320184635-7606e756e683 h1:khxVcsk/FhnzxMKOyD+TDGwjbEOpcPuIpmafPGFmhMA= -google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/genproto v0.0.0-20230524185152-1884fd1fac28 h1:+55/MuGJORMxCrkAgo2595fMAnN/4rweCuwibbqrvpc= +google.golang.org/genproto v0.0.0-20230524185152-1884fd1fac28/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 48946fd..29a98c7 100644 --- a/main.go +++ b/main.go @@ -1,11 +1,11 @@ package main import ( + "context" "flag" + "log" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" - + "github.com/hashicorp/terraform-plugin-framework/providerserver" "github.com/on2itsecurity/terraform-provider-auxo/auxo" ) @@ -15,11 +15,16 @@ func main() { flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") flag.Parse() - plugin.Serve(&plugin.ServeOpts{ - Debug: debug, - ProviderAddr: "registry.terraform.io/on2itsecurity/auxo", - ProviderFunc: func() *schema.Provider { - return auxo.Provider() + err := providerserver.Serve( + context.Background(), + auxo.New, + providerserver.ServeOpts{ + Debug: debug, + Address: "registry.terraform.io/on2itsecurity/auxo", }, - }) + ) + + if err != nil { + log.Fatal(err) + } } diff --git a/templates/data-sources/contact.md.tmpl b/templates/data-sources/contact.md.tmpl new file mode 100644 index 0000000..f51ff08 --- /dev/null +++ b/templates/data-sources/contact.md.tmpl @@ -0,0 +1,16 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile "examples/data-sources/contact.tf" }} + +{{ .SchemaMarkdown | trimspace }} diff --git a/templates/index.md.tmpl b/templates/index.md.tmpl new file mode 100644 index 0000000..b0e3355 --- /dev/null +++ b/templates/index.md.tmpl @@ -0,0 +1,59 @@ +--- +page_title: Provider: {{ .ProviderShortName | upper }} +subcategory: "" +description: |- + Terraform provider for interacting with AUXO. +--- + +# AUXO Provider + +This provider is build to interact with the [ON2IT AUXO Zero Trust Platform](https://on2it.net/managed-security/#section-auxo). + +When upgrading to version 0.0.7, the following changes might break your configuration. + +- Environment variable AUXOTOKEN is now AUXO_TOKEN +- Measure format within the `auxo_protectsurface` resource has changed. + +## Example Usage + +{{ tffile "examples/provider/provider.tf" }} + +Provide your AUXO token either with setting an environment variable `AUXO_TOKEN` (recommended) or by setting it in the provider configuration (not recommended). + +```shell +export AUXO_TOKEN= +``` + +### Example with token + +{{ tffile "examples/provider/provider_token.tf" }} + +### Example with config name + +{{ tffile "examples/provider/provider_config.tf" }} + +By default it will look for the configuration in `~/.ztctl/config.json`, which has the following format. This location can be overridden by setting the `config` attribute. + +The `name` configuration attribute will take precedence over the `token` and `url` attributes. + +```json +{ + "configs": [ + { + "alias": "tenant1", + "description": "This is the token for tenant 1", + "token": "VerySecureTokenTenant1", + "apiaddress": "api.on2it.dev", + "debug": false + }, + { + "alias": "tenant2", + "description": "This is the token for tenant 2", + "token": "VerySecureTokenTenant2", + "apiaddress": "api.on2it.net", + "debug": false + } + ] +} + +{{ .SchemaMarkdown | trimspace }} \ No newline at end of file diff --git a/templates/resources/location.md.tmpl b/templates/resources/location.md.tmpl new file mode 100644 index 0000000..de041d4 --- /dev/null +++ b/templates/resources/location.md.tmpl @@ -0,0 +1,16 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile "examples/resources/location.tf" }} + +{{ .SchemaMarkdown | trimspace }} diff --git a/templates/resources/protectsurface.md.tmpl b/templates/resources/protectsurface.md.tmpl new file mode 100644 index 0000000..149ecd5 --- /dev/null +++ b/templates/resources/protectsurface.md.tmpl @@ -0,0 +1,56 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile "examples/resources/protectsurface.tf" }} + +### Example uses of measures + +{{ tffile "examples/resources/protectsurface-measures.tf" }} + +{{ .SchemaMarkdown | trimspace }} + +### Default measures + +- flows-segmentation +- flows-restrict-outbound +- flows-restrict-inbound +- flows-appbased +- flows-contentbased +- flows-urlbased +- flows-behavioral +- endpoint-exploit +- endpoint-malware +- endpoint-ransomware +- encryption-ssl-inbound-decryption +- encryption-ssl-outbound-decryption +- encryption-at-rest +- encryption-in-transit +- orchestrate-roe +- threat-management-threat-intel +- threat-management-vulnerability-management +- ddos-volume +- ddos-targeted +- identity-centrally-managed +- identity-rbac +- identity-mfa +- identity-auditable +- secure-systems-software +- secure-systems-hardware +- secure-systems-updates +- devsecops-securebuild +- devsecops-supplychain +- logging-central +- data-backup +- data-phishing +- data-dlp +- data-classification diff --git a/templates/resources/state.md.tmpl b/templates/resources/state.md.tmpl new file mode 100644 index 0000000..078d552 --- /dev/null +++ b/templates/resources/state.md.tmpl @@ -0,0 +1,15 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile "examples/resources/state.tf" }} + diff --git a/templates/resources/transactionflow.md.tmpl b/templates/resources/transactionflow.md.tmpl new file mode 100644 index 0000000..71a9f77 --- /dev/null +++ b/templates/resources/transactionflow.md.tmpl @@ -0,0 +1,19 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +## Example Usage + +{{ tffile "examples/resources/transactionflow.tf" }} + +**Important** if a flow is allowed on protect surface A to go to protect surface B, it does not mean that the flow is accepted on protect surface B. There needs to be mutual consensus, which means two resources of transactionflow are needed, one for each protect surface, see example below. + +{{ tffile "examples/resources/transactionflow-bidirectional.tf" }} +