diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 314de6ee..01e33599 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -77,18 +77,23 @@ jobs: KEYFACTOR_PASSWORD: ${{secrets.KEYFACTOR_PASSWORD}} KEYFACTOR_DOMAIN: ${{secrets.KEYFACTOR_DOMAIN}} - KEYFACTOR_CERTIFICATE_ID: ${{secrets.KEYFACTOR_CERTIFICATE_ID}} - KEYFACTOR_CERTIFICATE_PASSWORD: ${{secrets.KEYFACTOR_CERTIFICATE_PASSWORD}} - KEYFACTOR_CERTIFICATE_TEMPLATE_NAME: ${{secrets.KEYFACTOR_CERTIFICATE_TEMPLATE_NAME}} - KEYFACTOR_CERTIFICATE_CA_DOMAIN: ${{secrets.KEYFACTOR_CERTIFICATE_CA_DOMAIN}} - KEYFACTOR_CERTIFICATE_CA_NAME: ${{secrets.KEYFACTOR_CERTIFICATE_CA_NAME}} + TEST_AGENT_ID: ${{secrets.TEST_AGENT_ID}} + TEST_AGENT_CLIENT_MACHINE_NAME: ${{secrets.TEST_AGENT_CLIENT_MACHINE_NAME}} - KEYFACTOR_CERTIFICATE_STORE_ID: ${{secrets.KEYFACTOR_CERTIFICATE_STORE_ID}} - KEYFACTOR_CERTIFICATE_STORE_CLIENT_MACHINE: ${{secrets.KEYFACTOR_CERTIFICATE_STORE_CLIENT_MACHINE}} - KEYFACTOR_CERTIFICATE_STORE_ORCHESTRATOR_AGENT_ID: ${{secrets.KEYFACTOR_CERTIFICATE_STORE_ORCHESTRATOR_AGENT_ID}} - KEYFACTOR_CERTIFICATE_STORE_CONTAINER_ID1: ${{secrets.KEYFACTOR_CERTIFICATE_STORE_CONTAINER_ID1}} - KEYFACTOR_CERTIFICATE_STORE_CONTAINER_ID2: ${{secrets.KEYFACTOR_CERTIFICATE_STORE_CONTAINER_ID2}} - KEYFACTOR_CERTIFICATE_STORE_PASS: ${{secrets.TEST_CERTSTORE_PASS}} + TEST_CERTIFICATE_ID: ${{secrets.TEST_CERTIFICATE_ID}} + TEST_CERTIFICATE_CN: ${{secrets.TEST_CERTIFICATE_CN}} + TEST_CERTIFICATE_THUMBPRINT: ${{secrets.TEST_CERTIFICATE_THUMBPRINT}} + TEST_CERTIFICATE_PASSWORD: ${{secrets.TEST_CERTIFICATE_PASSWORD}} + TEST_CERTIFICATE_TEMPLATE_NAME: ${{secrets.TEST_CERTIFICATE_TEMPLATE_NAME}} + TEST_CERTIFICATE_CA_DOMAIN: ${{secrets.TEST_CERTIFICATE_CA_DOMAIN}} + TEST_CERTIFICATE_CA_NAME: ${{secrets.TEST_CERTIFICATE_CA_NAME}} + + TEST_CERTIFICATE_STORE_ID: ${{secrets.TEST_CERTIFICATE_STORE_ID}} + TEST_CERTIFICATE_STORE_CLIENT_MACHINE: ${{secrets.TEST_CERTIFICATE_STORE_CLIENT_MACHINE}} + TEST_CERTIFICATE_STORE_ORCHESTRATOR_AGENT_ID: ${{secrets.TEST_CERTIFICATE_STORE_ORCHESTRATOR_AGENT_ID}} + TEST_CERTIFICATE_STORE_CONTAINER_ID1: ${{secrets.TEST_CERTIFICATE_STORE_CONTAINER_ID1}} + TEST_CERTIFICATE_STORE_CONTAINER_ID2: ${{secrets.TEST_CERTIFICATE_STORE_CONTAINER_ID2}} + TEST_CERTIFICATE_STORE_PASS: ${{secrets.TEST_CERTSTORE_PASS}} KEYFACTOR_DEPLOY_CERT_STOREID1: ${{secrets.KEYFACTOR_DEPLOY_CERT_STOREID1}} KEYFACTOR_DEPLOY_CERT_STOREID2: ${{secrets.KEYFACTOR_DEPLOY_CERT_STOREID2}} @@ -104,5 +109,5 @@ jobs: KEYFACTOR_TEMPLATE_ROLE_BINDING_TEMPLATE_NAME2: ${{secrets.KEYFACTOR_TEMPLATE_ROLE_BINDING_TEMPLATE_NAME2}} KEYFACTOR_TEMPLATE_ROLE_BINDING_TEMPLATE_NAME3: ${{secrets.KEYFACTOR_TEMPLATE_ROLE_BINDING_TEMPLATE_NAME3}} - run: go test -v -cover ./keyfactor + run: go test -v -cover ./internal/provider/v3/command timeout-minutes: 10 \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index debdc8f5..8a481ccb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,11 +7,11 @@ started with the development of Terraform providers is the [Terraform Plugin Doc 1. Clone the repository ```shell $ mkdir -p $GOPATH/src/github.com/Keyfactor; cd $GOPATH/src/github.com/Keyfactor - $ git clone git@github.com:Keyfactor/terraform-provider-keyfactor.git + $ git clone git@github.com:Keyfactor/terraform-provider-v2.git ``` 2. Enter the directory and build the provider ```shell - $ cd $GOPATH/src/github.com/Keyfactor/terraform-provider-keyfactor + $ cd $GOPATH/src/github.com/Keyfactor/terraform-provider-v2 $ make build ``` @@ -31,7 +31,7 @@ $ make testacc ``` or ```bash -$ go test github.com/Keyfactor/terraform-provider-keyfactor/keyfactor +$ go test github.com/Keyfactor/terraform-provider-v2/v2 ``` Note that the following environment variables must exist regardless of the test case: * ```KEYFACTOR_USERNAME``` diff --git a/GNUmakefile b/GNUmakefile index aa6988b5..b5642191 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -2,11 +2,11 @@ PROVIDER_DIR := $(PWD) TEST?=$$(go list ./... | grep -v 'vendor') HOSTNAME=keyfactor.com GOFMT_FILES := $$(find $(PROVIDER_DIR) -name '*.go' |grep -v vendor) -NAMESPACE=keyfactor -WEBSITE_REPO=https://github.com/Keyfactor/terraform-provider-keyfactor -NAME=keyfactor +NAMESPACE=command +WEBSITE_REPO=https://github.com/keyfactor-pub/terraform-provider-keyfactor +NAME=v3 BINARY=terraform-provider-${NAME} -VERSION=1.3.0 +VERSION=3.0.0 OS_ARCH := $(shell go env GOOS)_$(shell go env GOARCH) BASEDIR := ~/.terraform.d/plugins INSTALLDIR := ${BASEDIR}/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH} diff --git a/README.md b/README.md index b576f538..edf41afa 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ ___ ### Mac OS/Linux ```bash git clone https://github.com/Keyfactor/terraform-provider-keyfactor.git -cd terraform-provider-keyfactor +cd terraform-provider-v2 make install ``` diff --git a/docs/data-sources/certificate.md b/docs/data-sources/certificate.md index 3aef24ff..7951248b 100644 --- a/docs/data-sources/certificate.md +++ b/docs/data-sources/certificate.md @@ -3,12 +3,12 @@ page_title: "keyfactor_certificate Data Source - terraform-provider-keyfactor" subcategory: "" description: |- - + Example data source --- # keyfactor_certificate (Data Source) - +Example data source ## Example Usage @@ -19,9 +19,21 @@ provider "keyfactor" { hostname = "mykfinstance.kfdelivery.com" } -data "keyfactor_certificate" "protected_cert" { - id = "26" #Internal ID of the certificate - key_password = "my certificate password!" # This is bad practice. Use TF_VAR_ instead. +data "keyfactor_certificate" "cert_by_id" { + identifier = "26" # Lookup by Keyfactor Command Certificate ID +} + +data "keyfactor_certificate" "cert_by_id_w_collection_id" { + identifier = "26" # Lookup by Keyfactor Command Certificate ID + collection_id = 1 # Optional. If not specified, will search all collections. +} + +data "keyfactor_certificate" "cert_by_cn" { + identifier = "example.com" # Lookup by certificate CN. Will return the most recently issued certificate with this CN. +} + +data "keyfactor_certificate" "cert_by_cn_and_thumbprint" { + identifier = "1234567890ABCDEF1234567890ABCDEF12345678" # Lookup by certificate thumbprint. } ``` @@ -30,41 +42,221 @@ data "keyfactor_certificate" "protected_cert" { ### Required -- `id` (Number) Keyfactor certificate identifier. +- `identifier` (String) Keyfactor Command certificate identifier. This can be any of the following values: thumbprint, CN, or Keyfactor Command Certificate ID. If using CN to lookup the last issued certificate, the CN must be an exact match and if multiple certificates are returned the certificate that was most recently issued will be returned. ### Optional -- `collection_id` (Number) Optional certificate collection identifier used to ensure user access to the certificate. -- `key_password` (String, Sensitive) Optional, used to read the private key if it is password protected. -- `metadata` (Map of String) Metadata key-value pairs to be attached to certificate +- `collection_id` (Number) An integer indicating the Keyfactor Command reference ID of the collection to search for the certificate. Defaults to '0'. +- `include_private_key` (Boolean) A boolean indicating whether the private key should be retrieved from Keyfactor Command. Defaults to 'true'. +- `key_password` (String, Sensitive) A string containing the password to encrypt the private key with. ### Read-Only -- `certificate_authority` (String) Name of certificate authority to deploy certificate with Ex: Example Company CA 1 -- `certificate_chain` (String) PEM formatted certificate chain -- `certificate_pem` (String) PEM formatted certificate -- `certificate_template` (String) Short name of certificate template to be deployed -- `csr` (String) Base-64 encoded certificate signing request (CSR) -- `dns_sans` (List of String) List of DNS names to use as subjects of the certificate -- `ip_sans` (List of String) List of IPs to use as subjects of the certificate -- `issuer_dn` (String) Issuer distinguished name that signed the certificate -- `keyfactor_request_id` (Number) Keyfactor request ID necessary for deploying certificate -- `private_key` (String, Sensitive) PEM formatted PKCS#1 private key imported if cert_template has KeyRetention set to a value other than None, and the certificate was not enrolled using a CSR. -- `serial_number` (String) Serial number of newly enrolled certificate -- `subject` (Attributes) KeyfactorCertificate subject (see [below for nested schema](#nestedatt--subject)) -- `thumbprint` (String) Thumbprint of newly enrolled certificate -- `uri_sans` (List of String) List of URIs to use as subjects of the certificate - - -### Nested Schema for `subject` +- `archived_key` (Boolean) A boolean indicating whether the certificate has been archived. +- `ca_record_id` (String) A string containing the CA's reference ID for certificate. +- `ca_row_index` (Number) An integer containing the CA's reference ID for certificate. + | :exclamation: Note: The CARowIndex has been replaced by CARecordId, but will remain for backward compatibility. It will only contain a non-zero value for certificates issued by Microsoft CAs. For Microsoft CA certificates, the CARowIndex will be equal to the CARecordId value parsed to an integer. | + |----------------------------------------------------------------------------------------------------------------| +- `cert_state` (String) An integer specifying the state of the certificate. The following values are possible: + | Value | Description | + |-------|-------------| + | 0 | Unknown | + | 1 | Active | + | 2 | Revoked | + | 3 | Denied | + | 4 | Failed | + | 5 | Pending | + | 6 | Certificate Authority | + | 7 | Parent Certificate Authority | +- `certificate_authority` (String) A string indicating the name of the certificate authority that issued the certificate. +- `certificate_authority_id` (Number) An integer indicating the Keyfactor Command reference ID of the certificate authority that issued the certificate. +- `certificate_chain` (String) A string containing the certificate chain in PEM format. +- `certificate_id` (Number) This is the unique identifier for the certificate in Keyfactor Command. +- `certificate_key_id` (Number) An integer indicating the Keyfactor Command reference ID of the certificate key. +- `certificate_pem` (String) A string containing the certificate in PEM format. +- `certificate_template` (String) A string indicating the name of the template associated with the certificate. +- `command_request_id` (Number) An integer indicating the Keyfactor Command reference ID of the certificate request associated with the certificate. +- `content_bytes` (String) A string containing the certificate as bytes. +- `crl_distribution_point` (Attributes List) An array of objects containing the certificate revocation list (CRL) distribution points of the certificate. Each object contains the following fields: + | Field | Description | + |-------|-------------| + | Id | An integer indicating the Keyfactor Command reference ID of the CRL distribution point. | + | Url | A string indicating the URL of the CRL distribution point. | + | UrlHash | A string indicating the hash of the URL of the CRL distribution point. | (see [below for nested schema](#nestedatt--crl_distribution_point)) +- `curve` (String) A string indicating the OID of the elliptic curve algorithm configured for the certificate, for ECC templates. Well-known OIDs include: + - 1.2.840.10045.3.1.7 = P-256/prime256v1/secp256r1 + - 1.3.132.0.34 = P-384/secp384r1 + - 1.3.132.0.35 = P-521/secp521r1 +- `detailed_key_usage` (Attributes) An array of objects containing the detailed key usage of the certificate. Each object contains the following fields: + | Field | Description | + |-------|-------------| + | CrlSign | A boolean indicating whether the key can be used to sign a certificate revocation list (CRL). | + | DataEncipherment | A boolean indicating whether the key can be used for data encryption. | + | DecipherOnly | A boolean indicating whether the key can be used for decryption only. | + | DigitalSignature | A boolean indicating whether the key can be used for digital signatures. | + | EncipherOnly | A boolean indicating whether the key can be used for encryption only. | + | KeyAgreement | A boolean indicating whether the key can be used for key agreement. | + | KeyCertSign | A boolean indicating whether the key can be used to sign certificates. | + | KeyEncipherment | A boolean indicating whether the key can be used for key encryption. | + | NonRepudiation | A boolean indicating whether the key can be used for authentication. | + | HexCode | A string containing the hexadecimal code representing the total key usage. For example, a value of a0 would indicate digital signature with key encipherment. | (see [below for nested schema](#nestedatt--detailed_key_usage)) +- `dns_sans` (List of String) An array of strings containing the DNS subject alternative names of the certificate. +- `extended_key_usage` (Attributes List) An array of objects containing the extended key usage of the certificate. Each object contains the following fields: + | Field | Description | + |-------|-------------| + | Id | An integer indicating the Keyfactor Command reference ID of the extended key usage. | + | Oid | A string indicating the object identifier (OID) of the extended key usage. | + | DisplayName | A string indicating the display name of the extended key usage. | (see [below for nested schema](#nestedatt--extended_key_usage)) +- `has_private_key` (Boolean) A boolean indicating whether the certificate has a private key. +- `import_date` (String) The date, in UTC, on which the certificate was imported into Keyfactor Command. +- `ip_sans` (List of String) An array of strings containing the IP subject alternative names of the certificate. +- `issued_cn` (String) A string indicating the issued common name of the certificate. +- `issued_dn` (String) A string indicating the issued distinguished name of the certificate. +- `issued_email` (String) A string indicating the email address of the certificate. +- `issued_ou` (String) A string indicating the issued organizational unit of the certificate. +- `issuer_dn` (String) A string indicating the distinguished name of the certificate authority that issued the certificate. +- `key_bits` (Number) An integer specifying the key size in bits. +- `key_recoverable` (Boolean) A boolean indicating whether the certificate key is recoverable. +- `key_type` (String) A string indicating the type of key. +- `key_usage` (Number) An integer indicating the total key usage of the certificate. Key usage is stored in Active Directory as a single value made of a combination of values. The following values are possible: + | Value | Function | Description | + |-------|----------|-------------| + | 0 | None | No key usage | + | 1 | EncipherOnly | The key can be used for encryption only. | + | 2 | CRL Signing | The key can be used to sign a certificate revocation list (CRL). | + | 4 | Key CertSign | The key can be used to sign certificates. | + | 8 | Key Agreement | The key can be used for key agreement. | + | 16 | Data Encipherment | The key can be used for data encryption. | + | 32 | Key Encipherment | The key can be used for key encryption. | + | 64 | Non Repudiation | The key can be used for authentication. | + | 128 | Digital Signature | The key can be used for digital signatures. | + | 32768 | Decipherment Only | The key can be used for decryption only. | + + For example, a value of 160 would represent a key usage of digital signature with key encipherment. A value of 224 would add nonrepudiation to those. +- `location` (Object) An array of objects containing the certificate locations where the certificate is found using certificate store inventorying. Each object contains the following fields: + | Field | Description | + |-------|-------------| + | StoreMachineName | A string indicating the machine name of the certificate location. | + | StorePath | A string indicating the path of the certificate location. | + | StoreType | An integer indicating the type of the certificate location. | + | Alias | A string indicating the alias of the certificate location. | + | ChainLevel | An integer indicating the chain level of the certificate location. | + | CertStoreId | A string indicating the certificate store ID of the certificate location. | (see [below for nested schema](#nestedatt--location)) +- `locations_count` (Attributes List) An array of objects containing the certificate locations of the certificate. Each object contains the following fields: + | Field | Description | + |-------|-------------| + | Type | A string indicating the store type of the certificate location. | + | Count | An integer indicating the number of certificates in the certificate location. | (see [below for nested schema](#nestedatt--locations_count)) +- `metadata` (Map of String) +- `not_after` (String) The date, in UTC, on which the certificate expires. +- `not_before` (String) The date, in UTC, on which the certificate was issued by the certificate authority. +- `principal_id` (Number) An integer indicating the Keyfactor Command reference ID of the principal (UPN) that requested the certificate. Typically, this field is only populated for end user certificates requested through Keyfactor Command (e.g. Mac auto-enrollment certificates). See also PrinicpalName. +- `principal_name` (String) A string indicating the principal (UPN) that requested the certificate. Typically, this field is only populated for end user certificates requested through Keyfactor Command (e.g. Mac auto-enrollment certificates). See also PrincipalId. +- `private_key` (String, Sensitive) A string containing the private key in PEM format. +- `requester_id` (Number) An integer indicating the Keyfactor Command reference ID of the requester (UPN) that requested the certificate. Typically, this field is only populated for end user certificates requested through Keyfactor Command (e.g. Mac auto-enrollment certificates). See also RequesterName. +- `requester_name` (String) A string containing the name of the identity that requested the certificate. +- `revocation_comment` (String) A string indicating the comment associated with the revocation of the certificate. +- `revocation_effective_date` (String) The date, in UTC, on which the certificate was revoked. +- `revocation_reason` (Number) An integer indicating the reason the certificate was revoked. The following values are possible: + | Value | Description | + |-------|-------------| + | 0 | Unspecified | + | 1 | Key Compromise | + | 2 | CA Compromise | + | 3 | Affiliation Changed | + | 4 | Superseded | + | 5 | Cessation of Operation | + | 6 | Certificate Hold | + | 999 | Unknown | +- `serial_number` (String) A string indicating the serial number of the certificate. +- `signing_algorithm` (String) A string indicating the signing algorithm of the certificate. +- `ssl_location` (Attributes List) An array of objects containing the SSL locations where the certificate is found using SSL discovery. Each object contains the following fields: + | Field | Description | + |-------|-------------| + | StorePath | A string indicating the path of the SSL location. | + | AgentPool | A string indicating the agent pool of the SSL location. | + | IPAddress | A string indicating the IP address of the SSL location. | + | Port | An integer indicating the port of the SSL location. | + | NetworkName | A string indicating the network name of the SSL location. | (see [below for nested schema](#nestedatt--ssl_location)) +- `subject_alt_name_element` (Object) An array of objects containing the subject alternative name elements of the certificate. Each object contains the following fields: + | Field | Description | + |-------|-------------| + | Id | An integer indicating the Keyfactor Command reference ID of the subject alternative name element. | + | Value | A string indicating the value of the subject alternative name element. | + | Type | An integer indicating the type of the subject alternative name element. | + | ValueHash | A string indicating the hash of the value of the subject alternative name element. | (see [below for nested schema](#nestedatt--subject_alt_name_element)) +- `template_id` (Number) An integer indicating the Keyfactor Command reference ID of the template associated with the certificate. +- `thumbprint` (String) A string indicating the thumbprint of the certificate. +- `uri_sans` (List of String) An array of strings containing the URI subject alternative names of the certificate. + + +### Nested Schema for `crl_distribution_point` Read-Only: -- `subject_common_name` (String) Subject common name for new certificate -- `subject_country` (String) Subject country for new certificate -- `subject_locality` (String) Subject locality for new certificate -- `subject_organization` (String) Subject organization for new certificate -- `subject_organizational_unit` (String) Subject organizational unit for new certificate -- `subject_state` (String) Subject state for new certificate +- `id` (Number) +- `url` (String) +- `url_hash` (String) + + + +### Nested Schema for `detailed_key_usage` + +Read-Only: + +- `crl_sign` (Boolean) +- `data_encipherment` (Boolean) +- `decipher_only` (Boolean) +- `digital_signature` (Boolean) +- `encipher_only` (Boolean) +- `hex_code` (String) +- `key_agreement` (Boolean) +- `key_cert_sign` (Boolean) +- `key_encipherment` (Boolean) +- `non_repudiation` (Boolean) + + + +### Nested Schema for `extended_key_usage` + +Read-Only: + +- `display_name` (String) A string indicating the display name of the extended key usage. +- `id` (Number) An integer indicating the Keyfactor Command reference ID of the extended key usage. +- `oid` (String) A string indicating the object identifier (OID) of the extended key usage. + + + +### Nested Schema for `location` + +Read-Only: + + + + +### Nested Schema for `locations_count` + +Read-Only: + +- `count` (Number) +- `store_type` (String) + + + +### Nested Schema for `ssl_location` + +Read-Only: + +- `agent_pool` (String) +- `ip_address` (String) +- `network_name` (String) +- `port` (Number) +- `store_path` (String) + + + +### Nested Schema for `subject_alt_name_element` + +Read-Only: diff --git a/docs/data-sources/certificate_store.md b/docs/data-sources/certificate_store.md deleted file mode 100644 index 5f7d610d..00000000 --- a/docs/data-sources/certificate_store.md +++ /dev/null @@ -1,61 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "keyfactor_certificate_store Data Source - terraform-provider-keyfactor" -subcategory: "" -description: |- - ---- - -# keyfactor_certificate_store (Data Source) - - - -## Example Usage - -```terraform -provider "keyfactor" { - username = "COMMAND\\your_username" - password = "your_api_password" - hostname = "mykfinstance.kfdelivery.com" -} - -data "keyfactor_certificate_store" "iis_personal" { - id = "ef0b8005-63bf-42e8-aa3f-23bc94dcf611" - password = "your_personal_store_password" -} - -output "iis_personal_metadata" { - value = { - id = data.keyfactor_certificate_store.iis_personal.id - container_id = data.keyfactor_certificate_store.iis_personal.container_id - } -} -``` - - -## Schema - -### Required - -- `id` (String) Keyfactor certificate store GUID. - -### Optional - -- `agent_assigned` (Boolean) Bool indicating if there is an orchestrator assigned to the new certificate store. -- `approved` (Boolean) Bool that indicates the approval status of store created. Default is true, omit if unsure. -- `container_name` (String) Name of certificate store's associated container, if applicable. -- `create_if_missing` (Boolean) Bool that indicates if the store should be created with information provided. Valid only for JKS type, omit if unsure. -- `inventory_schedule` (String) Inventory schedule for new certificate store. -- `password` (String, Sensitive) Sets password for certificate store. -- `properties` (Map of String) Certificate properties specific to certificate store type configured as key-value pairs. -- `set_new_password_allowed` (Boolean) Indicates whether the store password can be changed. - -### Read-Only - -- `agent_id` (String) String indicating the Keyfactor Command GUID of the orchestrator for the created store. -- `client_machine` (String) Client machine name; value depends on certificate store type. See API reference guide -- `container_id` (Number) Container identifier of the store's associated certificate store container. -- `store_path` (String) Path to the new certificate store on a target. Format varies depending on type. -- `store_type` (String) Short name of certificate store type. See API reference guide - - diff --git a/docs/data-sources/certificate_template.md b/docs/data-sources/certificate_template.md deleted file mode 100644 index 24fa69ac..00000000 --- a/docs/data-sources/certificate_template.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "keyfactor_certificate_template Data Source - terraform-provider-keyfactor" -subcategory: "" -description: |- - ---- - -# keyfactor_certificate_template (Data Source) - - - -## Example Usage - -```terraform -provider "keyfactor" { - username = "COMMAND\\your_username" - password = "your_api_password" - hostname = "mykfinstance.kfdelivery.com" -} - -data "keyfactor_certificate_template" "webserver_template" { - short_name = "2yrWebServer" #The template shortname of an existing certificate template to reference. -} -``` - - -## Schema - -### Required - -- `short_name` (String) A string containing the common name (short name) of the template. This name typically does not contain spaces. For a template created using a Microsoft management tool, this will be the Microsoft template name. This field is populated from Active Directory and is not configurable. - -### Optional - -- `allowed_requesters` (List of String) An array containing the list of Keyfactor Command security templates—as strings—that have been granted enroll permission on the template. - -### Read-Only - -- `allowed_enrollment_types` (Number) An integer indicating the type of enrollment allowed for the certificate template. Setting these options causes the template to appear in dropdowns in the corresponding section of the Management Portal. In the case of CSR Enrollment and PFX Enrollment, the templates only appear in dropdowns on the enrollment pages if they are available for enrollment from a CA also configured for enrollment within Keyfactor Command. -- `enrollment_fields` (List of Map of String) An array containing custom enrollment fields. These are configured on a per-template basis to allow you to submit custom fields with CSR enrollments and PFX enrollments to supply custom request attributes to the CA during the enrollment process. -- `forest_root` (String) Forest root that the template is stored under/created by -- `friendly_name` (String) Forest root that the template is stored under/created by -- `id` (Number) An integer indicating the ID of the template in Keyfactor Command. -- `key_archival` (Boolean) A Boolean indicating whether the template has been configured with the key archival setting in Active Directory (true) or not (false). This is a reference field and is not configurable. -- `key_retention` (String) A string indicating the type of key retention certificates enrolled with this template will use to store their private key in Keyfactor Command. ClosedShow key retention details. -- `key_retention_days` (Number) Duration that the private key should be retained -- `key_size` (String) A string indicating the minimum supported key size of the template. This field is populated from Active Directory and is not configurable. -- `key_type` (String) A string indicating the key type of the template. This field is populated from Active Directory and is not configurable. -- `key_usage` (Number) An integer indicating the total key usage of the certificate. Key usage is stored in Active Directory as a single value made of a combination of values. -- `name` (String) A string containing the name of the template. For a template created using a Microsoft management tool, this will be the Microsoft template display name. This field is populated from Active Directory and is not configurable. -- `oid` (String) A string containing the object ID of the template in Active Directory. This field is populated from Active Directory and is not configurable. -- `requires_approval` (Boolean) A Boolean indicating whether certificate enrollments require approval (true) or not (false). -- `rfc_enforcement` (Boolean) A Boolean indicating whether certificate enrollments made through Keyfactor Command for this template must include at least one DNS SAN (true) or not (false). In the Keyfactor Command Management Portal, this causes the CN entered in PFX enrollment to automatically be replicated as a SAN, which the user can either change or accept. -- `template_regexes` (List of String) List of regexes that the template will be matched against during enrollment. - - diff --git a/docs/data-sources/identity.md b/docs/data-sources/identity.md deleted file mode 100644 index e4fbcf44..00000000 --- a/docs/data-sources/identity.md +++ /dev/null @@ -1,70 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "keyfactor_identity Data Source - terraform-provider-keyfactor" -subcategory: "" -description: |- - ---- - -# keyfactor_identity (Data Source) - - - -## Example Usage - -```terraform -provider "keyfactor" { - username = "COMMAND\\your_username" - password = "your_api_password" - hostname = "mykfinstance.kfdelivery.com" -} - -# User example -data "keyfactor_identity" "kf_user" { - account_name = "my-kf-domain\\my-kf-account-name" # The name of the existing identity you want to reference. -} - -output "user_metadata" { - value = { - id = keyfactor_identity.kf_user.id - account_name = keyfactor_identity.kf_user.account_name - roles = keyfactor_identity.kf_user.roles - type = keyfactor_identity.kf_user.identity_type - valid = keyfactor_identity.kf_user.valid - } -} - -# Group example -data "keyfactor_identity" "kf_admins" { - account_name = "COMMAND\\Keyfactor-Admins" -} - -output "group_metadata" { - value = { - id = data.keyfactor_identity.kf_admins.id - account_name = data.keyfactor_identity.kf_admins.account_name - roles = data.keyfactor_identity.kf_admins.roles - type = data.keyfactor_identity.kf_admins.identity_type - valid = data.keyfactor_identity.kf_admins.valid - } -} -``` - - -## Schema - -### Required - -- `account_name` (String) A string containing the account name for the security identity. For Active Directory user and groups, this will be in the form DOMAIN\\user or group name - -### Optional - -- `roles` (List of String) An array containing the role IDs that the identity is attached to. - -### Read-Only - -- `id` (Number) An integer containing the Keyfactor Command identifier for the security identity. -- `identity_type` (String) A string indicating the type of identity—User or Group. -- `valid` (Boolean) A Boolean that indicates whether the security identity's audit XML is valid (true) or not (false). A security identity may become invalid if Keyfactor Command determines that it appears to have been tampered with. - - diff --git a/docs/data-sources/role.md b/docs/data-sources/role.md deleted file mode 100644 index c9e82f05..00000000 --- a/docs/data-sources/role.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "keyfactor_role Data Source - terraform-provider-keyfactor" -subcategory: "" -description: |- - ---- - -# keyfactor_role (Data Source) - - - -## Example Usage - -```terraform -provider "keyfactor" { - username = "COMMAND\\your_username" - password = "your_api_password" - hostname = "mykfinstance.kfdelivery.com" -} - -data "keyfactor_role" "admin" { - name = "Administrator" -} -``` - - -## Schema - -### Required - -- `name` (String) An string associated with a Keyfactor security role. - -### Read-Only - -- `description` (String) A string containing the description of the role in Keyfactor -- `id` (Number) Internal ID of the role. -- `permissions` (List of String) An array containing the permissions assigned to the role in a list of Name:Value pairs - - diff --git a/docs/index.md b/docs/index.md index ad608a0a..8da64347 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,6 +13,15 @@ description: |- ## Example Usage ```terraform +terraform { + required_providers { + keyfactor = { + version = "3.0.0" + source = "keyfactor-pub/keyfactor" + } + } +} + provider "keyfactor" { username = "COMMAND\\your_username" password = "your_api_password" @@ -26,8 +35,10 @@ provider "keyfactor" { ### Optional -- `appkey` (String, Sensitive) Application key provisioned by Keyfactor instance. This can also be set via the `KEYFACTOR_APPKEY` environment variable. -- `domain` (String) Domain that Keyfactor instance is hosted on. This can also be set via the `KEYFACTOR_DOMAIN` environment variable. -- `hostname` (String) Hostname of Keyfactor instance. Ex: keyfactor.examplecompany.com. This can also be set via the `KEYFACTOR_HOSTNAME` environment variable. -- `password` (String, Sensitive) Password of Keyfactor service account. This can also be set via the `KEYFACTOR_PASSWORD` environment variable. -- `username` (String) Username of Keyfactor service account. This can also be set via the `KEYFACTOR_USERNAME` environment variable. +- `auth_config` (String, Sensitive) The path to the auth config file to use for authentication to Keyfactor Command. This can be used in place of the username, password, domain, client_id, and client_secret fields. +- `client_id` (String, Sensitive) The client ID to authenticate to Keyfactor Command, if using oauth. Must be used in conjunction with client_secret. +- `client_secret` (String, Sensitive) The client secret to authenticate to Keyfactor Command, if using oauth. Must be used in conjunction with client_id. +- `domain` (String, Sensitive) The active directory domain of the username used to authenticate to Keyfactor Command, if using basic auth. This can be provided in the username field as well by using one of the following patterns: '\username' or 'username@domain'. +- `hostname` (String, Sensitive) Keyfactor Command hostname including any non-standard port. +- `password` (String, Sensitive) The password used to authenticate to Keyfactor Command, if using basic auth. Must be used in conjunction with username. +- `username` (String, Sensitive) The username used to authenticate to Keyfactor Command, if using basic auth. Must be used in conjunction with password. diff --git a/docs/resources/certificate.md b/docs/resources/certificate.md deleted file mode 100644 index b0a5d0a6..00000000 --- a/docs/resources/certificate.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "keyfactor_certificate Resource - terraform-provider-keyfactor" -subcategory: "" -description: |- - ---- - -# keyfactor_certificate (Resource) - - - -## Example Usage - -```terraform -provider "keyfactor" { - username = "your_username" - password = "your_api_password" - hostname = "mykfinstance.kfdelivery.com" - domain = "mydomain.com" -} - -## PFX Enrollment -resource "keyfactor_certificate" "PFXCertificate" { - subject = { - # Certificate subject metadata - subject_common_name = "mypfx.kfdelivery.com" - subject_organization = "Keyfactor" - subject_locality = "Cleveland" - subject_country = "US" - subject_organizational_unit = "Software Development" - subject_state = "OH" - } - - # Optional SANs - ip_sans = ["192.168.123.2", "172.51.2.4"] # Optional IP SANs - dns_sans = ["meow.example.com", "meow2"] # Optional DNS SANs - key_password = "my certificate password!" - # The password for the certificate. Note: This is bad practice, use TF_VAR_ instead. - certificate_authority = "COMMAND\\MY_CA_01" # Keyfactor CA to use to handle the certificate request. - certificate_template = "2yrWebServer" # The template shortname to use for the certificate. -} - -## CSR Enrollment -resource "tls_private_key" "rsa_4096" { - # Create a private key for the certificate request. - algorithm = "RSA" - rsa_bits = 4096 -} - -resource "tls_cert_request" "csr" { - # Create a CSR using the TLS private key above - private_key_pem = tls_private_key.rsa_4096.private_key_pem - - subject { - common_name = "mycsr.kfdelivery.com" - organization = "Keyfactor" - country = "US" - locality = "Seattle" - organizational_unit = "DevOps Enablement" - postal_code = "12345" - province = "WA" - street_address = ["123 Main St", "Suite 1", "Second Floor", "Downtown"] - } -} - -resource "keyfactor_certificate" "kf_csr_cert" { - csr = tls_cert_request.csr.cert_request_pem - certificate_authority = "COMMAND\\MY_CA_01" - certificate_template = "2yrWebServer" - - dns_sans = ["mycsr.kfdelivery.com"] # Optional DNS SANs - ip_sans = ["172.16.0.2", "192.168.0.2"] # Optional IP SANs - uri_sans = ["https://mycsr.kfdelivery.com"] # Optional URI SANs - metadata = { - "Email-Contact" = "my_username@mydomain.com" - # Note: metadata keys must be defined in Keyfactor and cannot just be arbitrarily added - } -} -``` - - -## Schema - -### Required - -- `certificate_authority` (String) Name of certificate authority to deploy certificate with Ex: Example Company CA 1 -- `certificate_template` (String) Short name of certificate template to be deployed - -### Optional - -- `collection_id` (Number) Optional certificate collection identifier used to ensure user access to the certificate. -- `csr` (String) Base-64 encoded certificate signing request (CSR) -- `dns_sans` (List of String) List of DNS names to use as subjects of the certificate -- `ip_sans` (List of String) List of DNS names to use as subjects of the certificate -- `key_password` (String, Sensitive) Password to protect certificate and private key with -- `metadata` (Map of String) Metadata key-value pairs to be attached to certificate -- `subject` (Attributes) KeyfactorCertificate subject (see [below for nested schema](#nestedatt--subject)) -- `uri_sans` (List of String) List of URIs to use as subjects of the certificate - -### Read-Only - -- `certificate_chain` (String) PEM formatted certificate chain -- `certificate_pem` (String) PEM formatted certificate -- `id` (Number) Keyfactor certificate ID -- `issuer_dn` (String) Issuer distinguished name that signed the certificate -- `keyfactor_request_id` (Number) Keyfactor request ID necessary for deploying certificate -- `private_key` (String, Sensitive) PEM formatted PKCS#1 private key imported if cert_template has KeyRetention set to a value other than None, and the certificate was not enrolled using a CSR. -- `serial_number` (String) Serial number of newly enrolled certificate -- `thumbprint` (String) Thumbprint of newly enrolled certificate - - -### Nested Schema for `subject` - -Optional: - -- `subject_common_name` (String) Subject common name for new certificate -- `subject_country` (String) Subject country for new certificate -- `subject_locality` (String) Subject locality for new certificate -- `subject_organization` (String) Subject organization for new certificate -- `subject_organizational_unit` (String) Subject organizational unit for new certificate -- `subject_state` (String) Subject state for new certificate - -## Import - -Import is supported using the following syntax: - -```shell -terraform import keyfactor_certificate.mycert 65 # Where this is the ID of the certificate on Keyfactor -``` diff --git a/docs/resources/certificate_deployment.md b/docs/resources/certificate_deployment.md deleted file mode 100644 index 717a870c..00000000 --- a/docs/resources/certificate_deployment.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "keyfactor_certificate_deployment Resource - terraform-provider-keyfactor" -subcategory: "" -description: |- - ---- - -# keyfactor_certificate_deployment (Resource) - - - -## Example Usage - -```terraform -provider "keyfactor" { - username = "COMMAND\\your_username" - password = "your_api_password" - hostname = "mykfinstance.kfdelivery.com" - domain = "mydomain.com" -} - -## PFX Enrollment -resource "keyfactor_certificate" "pfx_cert" { - subject = { - subject_common_name = "ShortlivedTest7622-test" - } - dns_sans = ["ShortlivedTest7622-test"] - - key_password = "don't put secrets in code!" // Please don't use this password in production pass in an environmental variable or something - certificate_authority = "\\" - certificate_template = "" -} - -# Deploy the created PFX certificate to all created cert stores -resource "keyfactor_certificate_deployment" "pfx_cert" { - certificate_id = keyfactor_certificate.pfx_cert.id # ID of the certificate to deploy - certificate_store_id = "" # ID of the certificate store to deploy to - certificate_alias = "" # Alias to use for the certificate in the store -} -``` - - -## Schema - -### Required - -- `certificate_alias` (String) A string providing an alias to be used for the certificate upon entry into the certificate store. The function of the alias varies depending on the certificate store type. Please ensure that the alias is lowercase, or problems can arise in Terraform Plan. -- `certificate_id` (Number) Keyfactor certificate ID -- `certificate_store_id` (String) A string containing the GUID for the certificate store to which the certificate should be added. - -### Optional - -- `key_password` (String, Sensitive) Password that protects PFX certificate, if the certificate was enrolled using PFX enrollment, or is password protected in general. This value cannot change, and Terraform will throw an error if a change is attempted. - -### Read-Only - -- `id` (String) A unique identifier for this certificate deployment. - - diff --git a/docs/resources/certificate_store.md b/docs/resources/certificate_store.md deleted file mode 100644 index 31fd5c5d..00000000 --- a/docs/resources/certificate_store.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "keyfactor_certificate_store Resource - terraform-provider-keyfactor" -subcategory: "" -description: |- - ---- - -# keyfactor_certificate_store (Resource) - - - -## Example Usage - -```terraform -provider "keyfactor" { - username = "COMMAND\\your_username" - password = "your_api_password" - hostname = "mykfinstance.kfdelivery.com" - domain = "mydomain.com" -} - -resource "keyfactor_certificate_store" "iis_trusted_roots" { - client_machine = "myorchestrator01" # Orchestrator client name - store_path = "IIS Trusted Roots" # Varies based on store type - agent_id = "c2b2084f-3d89-4ded-bb8b-b4e0e74d2b59" # Orchestrator GUID - store_type = "IIS" # Must exist in KeyFactor - properties = { - # Optional properties based on the store type - UseSSL = true - } - inventory_schedule = "60m" # How often to update the inventory - container_id = 2 # ID of the KeyFactor container - password = "my store password!" - # The password for the certificate store. Note: This is bad practice, use TF_VAR_ instead. -} -``` - - -## Schema - -### Required - -- `agent_id` (String) String indicating the Keyfactor Command GUID of the orchestrator for the created store. -- `client_machine` (String) Client machine name; value depends on certificate store type. See API reference guide -- `store_path` (String) Path to the new certificate store on a target. Format varies depending on type. -- `store_type` (String) Short name of certificate store type. See API reference guide - -### Optional - -- `agent_assigned` (Boolean) Bool indicating if there is an orchestrator assigned to the new certificate store. -- `approved` (Boolean) Bool that indicates the approval status of store created. Default is true, omit if unsure. -- `container_id` (Number) Container identifier of the store's associated certificate store container. -- `container_name` (String) Name of certificate store's associated container, if applicable. -- `create_if_missing` (Boolean) Bool that indicates if the store should be created with information provided. Valid only for JKS type, omit if unsure. -- `inventory_schedule` (String) Inventory schedule for new certificate store. -- `password` (String) Sets password for certificate store. -- `properties` (Map of String) Certificate properties specific to certificate store type configured as key-value pairs. -- `set_new_password_allowed` (Boolean) Indicates whether the store password can be changed. - -### Read-Only - -- `id` (String) Keyfactor certificate store GUID. - -## Import - -Import is supported using the following syntax: - -```shell -terraform import keyfactor_certificate_store.mystore "9f8855f1-80ff-4475-89ec-d82accb32cea" # Where this is the GUID of the certificate store -``` diff --git a/docs/resources/identity.md b/docs/resources/identity.md deleted file mode 100644 index 1657eb3e..00000000 --- a/docs/resources/identity.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "keyfactor_identity Resource - terraform-provider-keyfactor" -subcategory: "" -description: |- - ---- - -# keyfactor_identity (Resource) - - - -## Example Usage - -```terraform -provider "keyfactor" { - username = "COMMAND\\your_username" - password = "your_api_password" - hostname = "mykfinstance.kfdelivery.com" - domain = "mydomain.com" -} - -resource "keyfactor_identity" "identity" { - account_name = "COMMAND\\your_username" # your_domain\\your_username - roles = ["EnrollPFX", "Administrator", "Nginx"] # List of existing role names to assign to the identity -} -``` - - -## Schema - -### Required - -- `account_name` (String) A string containing the account name for the security identity. For Active Directory user and groups, this will be in the form DOMAIN\\user or group name - -### Optional - -- `roles` (List of String) An array containing the role IDs that the identity is attached to. - -### Read-Only - -- `id` (Number) An integer containing the Keyfactor Command identifier for the security identity. -- `identity_type` (String) A string indicating the type of identity—User or Group. -- `valid` (Boolean) A Boolean that indicates whether the security identity's audit XML is valid (true) or not (false). A security identity may become invalid if Keyfactor Command determines that it appears to have been tampered with. - -## Import - -Import is supported using the following syntax: - -```shell -terraform import keyfactor_security_identity.identity 'mykfdomain\\myusername' # The user/group name to import -``` diff --git a/docs/resources/role.md b/docs/resources/role.md deleted file mode 100644 index f5e8734e..00000000 --- a/docs/resources/role.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "keyfactor_role Resource - terraform-provider-keyfactor" -subcategory: "" -description: |- - ---- - -# keyfactor_role (Resource) - - - -## Example Usage - -```terraform -provider "keyfactor" { - username = "COMMAND\\your_username" - password = "your_api_password" - hostname = "mykfinstance.kfdelivery.com" - domain = "mydomain.com" -} - -resource "keyfactor_role" "kf_terraform_role" { - name = "Terraform" # Name of the role to create - description = "Role used to demonstrate Keyfactor's ability to integrate with Terraform." - # Description of the role to create - permissions = distinct([ - # List of valid permissions to assign to the role - "AdminPortal:Read", - "AdminPortal:Read", #Note the duplicate entry here - "Certificates:Read", - "Certificates:EditMetadata", - "Certificates:Import", - "Certificates:Recover", - "Certificates:Revoke", - "CertificateCollections:Modify", - "PkiManagement:Read", - "PkiManagement:Modify", - "CertificateStoreManagement:Read", - "CertificateStoreManagement:Modify", - "API:Read", - "CertificateStoreManagement:Schedule", - "CertificateEnrollment:EnrollPFX", - "CertificateEnrollment:EnrollCSR", - "Certificates:Delete", - "Certificates:ImportPrivateKey", - "CertificateEnrollment:CsrGeneration", - "CertificateEnrollment:PendingCsr" - ]) -} -``` - - -## Schema - -### Required - -- `description` (String) A string containing the description of the role in Keyfactor -- `name` (String) An string associated with a Keyfactor security role. - -### Optional - -- `permissions` (List of String) An array containing the permissions assigned to the role in a list of Name:Value pairs - -### Read-Only - -- `id` (Number) The ID of this resource. - -## Import - -Import is supported using the following syntax: - -```shell -terraform import keyfactor_security_role.myrole "My Role" # The name of the role to import -``` diff --git a/docs/resources/template_role_binding.md b/docs/resources/template_role_binding.md deleted file mode 100644 index 41a27a1a..00000000 --- a/docs/resources/template_role_binding.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "keyfactor_template_role_binding Resource - terraform-provider-keyfactor" -subcategory: "" -description: |- - ---- - -# keyfactor_template_role_binding (Resource) - - - -## Example Usage - -```terraform -provider "keyfactor" { - username = "COMMAND\\your_username" - password = "your_api_password" - hostname = "mykfinstance.kfdelivery.com" - domain = "mydomain.com" -} - -resource "keyfactor_template_role_binding" "kf_terraform_role_attachment" { - role_name = "WebServerTerraformer" # The name of the role to grant template access to. - template_short_names = ["2YearTestWebServer", "2yrWebServer"] - # List of short names of templates the role will have access to. -} -``` - - -## Schema - -### Required - -- `role_name` (String) An string associated with a Keyfactor security role being attached. This is just the name field found on Keyfactor. - -### Optional - -- `template_short_names` (List of String) A list of certificate template short name in Keyfactor that the role will be attached to. - -### Read-Only - -- `id` (String) ID of template role binding. - - diff --git a/examples/data-sources/keyfactor_agent/data-source.tf b/examples/data-sources/keyfactor_agent/data-source.tf new file mode 100644 index 00000000..bada6b28 --- /dev/null +++ b/examples/data-sources/keyfactor_agent/data-source.tf @@ -0,0 +1,14 @@ +provider "keyfactor" { + username = "COMMAND\\your_username" + password = "your_api_password" + hostname = "mykfinstance.kfdelivery.com" +} + +data "keyfactor_agent" "agent_from_guid" { + agent_identifier = "00000000-0000-0000-0000-000000000000" # Lookup by agent GUID +} + +data "keyfactor_agent" "agent_from_client_machine_name" { + agent_identifier = "my_client_machine" # Lookup by client machine name +} + diff --git a/examples/data-sources/keyfactor_certificate/data-source.tf b/examples/data-sources/keyfactor_certificate/data-source.tf index 1e169779..6b7b776a 100644 --- a/examples/data-sources/keyfactor_certificate/data-source.tf +++ b/examples/data-sources/keyfactor_certificate/data-source.tf @@ -4,7 +4,20 @@ provider "keyfactor" { hostname = "mykfinstance.kfdelivery.com" } -data "keyfactor_certificate" "protected_cert" { - id = "26" #Internal ID of the certificate - key_password = "my certificate password!" # This is bad practice. Use TF_VAR_ instead. -} \ No newline at end of file +data "keyfactor_certificate" "cert_by_id" { + identifier = "26" # Lookup by Keyfactor Command Certificate ID +} + +data "keyfactor_certificate" "cert_by_id_w_collection_id" { + identifier = "26" # Lookup by Keyfactor Command Certificate ID + collection_id = 1 # Optional. If not specified, will search all collections. +} + +data "keyfactor_certificate" "cert_by_cn" { + identifier = "example.com" # Lookup by certificate CN. Will return the most recently issued certificate with this CN. +} + +data "keyfactor_certificate" "cert_by_cn_and_thumbprint" { + identifier = "1234567890ABCDEF1234567890ABCDEF12345678" # Lookup by certificate thumbprint. +} + diff --git a/examples/provider/provider.tf b/examples/provider/provider.tf index cec42579..20351f2b 100644 --- a/examples/provider/provider.tf +++ b/examples/provider/provider.tf @@ -1,3 +1,12 @@ +terraform { + required_providers { + keyfactor = { + version = "3.0.0" + source = "keyfactor-pub/keyfactor" + } + } +} + provider "keyfactor" { username = "COMMAND\\your_username" password = "your_api_password" diff --git a/examples/resources/keyfactor_certificate/resource.tf b/examples/resources/keyfactor_certificate/resource.tf index 69c4ca4d..d304d7ff 100644 --- a/examples/resources/keyfactor_certificate/resource.tf +++ b/examples/resources/keyfactor_certificate/resource.tf @@ -19,7 +19,7 @@ resource "keyfactor_certificate" "PFXCertificate" { # Optional SANs ip_sans = ["192.168.123.2", "172.51.2.4"] # Optional IP SANs - dns_sans = ["san1.example.com", "san2"] # Optional DNS SANs + dns_sans = ["san1.example.com", "san2"] # Optional DNS SANs key_password = "my certificate password!" # The password for the certificate. Note: This is bad practice, use TF_VAR_ instead. certificate_authority = "COMMAND\\MY_CA_01" # Keyfactor CA to use to handle the certificate request. diff --git a/get_env.sh b/get_env.sh index 9726e650..cebdc552 100755 --- a/get_env.sh +++ b/get_env.sh @@ -64,3 +64,4 @@ KEYFACTOR_TEMPLATE_ROLE_BINDING_TEMPLATE_NAME3="User" printenv | grep KEYFACTOR > .env +terraform init \ No newline at end of file diff --git a/go.mod b/go.mod index 39fab452..e4a004cb 100644 --- a/go.mod +++ b/go.mod @@ -1,61 +1,77 @@ module github.com/keyfactor-pub/terraform-provider-keyfactor -go 1.18 +go 1.21 require ( - github.com/Keyfactor/keyfactor-go-client v1.3.4 - github.com/hashicorp/terraform-plugin-framework v0.10.0 - github.com/hashicorp/terraform-plugin-go v0.14.3 - github.com/hashicorp/terraform-plugin-log v0.8.0 - github.com/hashicorp/terraform-plugin-sdk/v2 v2.21.0 + github.com/Keyfactor/keyfactor-go-client-sdk/v2 v2.0.0-rc5 + github.com/getsentry/sentry-go v0.25.0 + github.com/hashicorp/terraform-plugin-docs v0.16.0 + github.com/hashicorp/terraform-plugin-framework v1.4.2 + github.com/hashicorp/terraform-plugin-go v0.19.1 + github.com/hashicorp/terraform-plugin-log v0.9.0 + github.com/hashicorp/terraform-plugin-testing v1.6.0 + software.sslmate.com/src/go-pkcs12 v0.4.0 ) require ( + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.1.1 // indirect + github.com/Masterminds/sprig/v3 v3.2.2 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect github.com/agext/levenshtein v1.2.2 // indirect - github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/armon/go-radix v1.0.0 // indirect + github.com/bgentry/speakeasy v0.1.0 // indirect + github.com/cloudflare/circl v1.3.3 // indirect github.com/fatih/color v1.13.0 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/google/go-cmp v0.5.9 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/uuid v1.3.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect - github.com/hashicorp/go-hclog v1.4.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.8 // indirect + github.com/hashicorp/go-plugin v1.5.2 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-version v1.6.0 // indirect - github.com/hashicorp/hc-install v0.4.0 // indirect - github.com/hashicorp/hcl/v2 v2.13.0 // indirect + github.com/hashicorp/hc-install v0.6.1 // indirect + github.com/hashicorp/hcl/v2 v2.19.1 // indirect github.com/hashicorp/logutils v1.0.0 // indirect - github.com/hashicorp/terraform-exec v0.17.2 // indirect - github.com/hashicorp/terraform-json v0.14.0 // indirect - github.com/hashicorp/terraform-registry-address v0.1.0 // indirect - github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect + github.com/hashicorp/terraform-exec v0.19.0 // indirect + github.com/hashicorp/terraform-json v0.18.0 // indirect + github.com/hashicorp/terraform-plugin-sdk/v2 v2.30.0 // indirect + github.com/hashicorp/terraform-registry-address v0.2.3 // indirect + github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect - github.com/imdario/mergo v0.3.13 // indirect - github.com/kr/pretty v0.3.0 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/huandu/xstrings v1.3.2 // indirect + github.com/imdario/mergo v0.3.15 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mitchellh/cli v1.1.5 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-wordwrap v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/oklog/run v1.0.0 // indirect - github.com/spbsoluble/go-pkcs12 v0.3.1 // indirect + github.com/posener/complete v1.2.3 // indirect + github.com/russross/blackfriday v1.6.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/spf13/cast v1.5.0 // 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.1 // indirect - github.com/zclconf/go-cty v1.10.0 // indirect - go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect - golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect - golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect - golang.org/x/text v0.4.0 // indirect - google.golang.org/appengine v1.6.6 // indirect - google.golang.org/genproto v0.0.0-20200711021454-869866162049 // indirect - google.golang.org/grpc v1.51.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/zclconf/go-cty v1.14.1 // indirect + golang.org/x/crypto v0.16.0 // indirect + golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 // indirect + golang.org/x/mod v0.13.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect + google.golang.org/grpc v1.59.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect ) diff --git a/go.sum b/go.sum index 37d48eab..156265f4 100644 --- a/go.sum +++ b/go.sum @@ -1,307 +1,305 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Keyfactor/keyfactor-go-client v1.3.4 h1:Dgl1vd8td4gWyrFKRNKlhkLrHqZhdKdwgjIlsLyXV+s= -github.com/Keyfactor/keyfactor-go-client v1.3.4/go.mod h1:u1M1AjcwiO/Tbvc7EsNl9YTy757hO5wmey1/W/7Qkbs= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= -github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= -github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= -github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Keyfactor/keyfactor-go-client-sdk/v2 v2.0.0-rc5 h1:vlKk0BbcJqF5TglRjyCakFoKnVoCfYLhKGwvMs/oytw= +github.com/Keyfactor/keyfactor-go-client-sdk/v2 v2.0.0-rc5/go.mod h1:KC5Kp96urx5wuAVJk9UUOZk0GR4Yku7DDesd7P58Jq0= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig/v3 v3.2.1/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= +github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= +github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= +github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFUye+ZcSR6opIgz9Co7WcDx6ZcY+RjfFHoA0I= -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/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= 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/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= -github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= -github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= -github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= -github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= -github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= -github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= +github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI= +github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git/v5 v5.9.0 h1:cD9SFA7sHVRdJ7AYck1ZaAa/yeuBvGPxwXDL8cxrObY= +github.com/go-git/go-git/v5 v5.9.0/go.mod h1:RKIqga24sWdMGZF+1Ekv9kylsDz6LzdTSI2s/OsZWE0= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 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.2/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.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +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.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= 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.4.0 h1:ctuWFGrhFha8BnnzxqeRGidlEcQkDyL5u8J8t5eA11I= -github.com/hashicorp/go-hclog v1.4.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +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.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 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.8 h1:CHGwpxYDOttQOY7HOWgETU9dyVjOXzniXDqJcYJE1zM= -github.com/hashicorp/go-plugin v1.4.8/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= +github.com/hashicorp/go-plugin v1.5.2 h1:aWv8eimFqWlsEiMrYZdPYl+FdHaBJSN4AWwGWfT1G2Y= +github.com/hashicorp/go-plugin v1.5.2/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 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.5.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/hc-install v0.4.0 h1:cZkRFr1WVa0Ty6x5fTvL1TuO1flul231rWkGH92oYYk= -github.com/hashicorp/hc-install v0.4.0/go.mod h1:5d155H8EC5ewegao9A4PUTMNPZaq+TbOzkJJZ4vrXeI= -github.com/hashicorp/hcl/v2 v2.13.0 h1:0Apadu1w6M11dyGFxWnmhhcMjkbAiKCv7G1r/2QgCNc= -github.com/hashicorp/hcl/v2 v2.13.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0= +github.com/hashicorp/hc-install v0.6.1 h1:IGxShH7AVhPaSuSJpKtVi/EFORNjO+OYVJJrAtGG2mY= +github.com/hashicorp/hc-install v0.6.1/go.mod h1:0fW3jpg+wraYSnFDJ6Rlie3RvLf1bIqVIkzoon4KoVE= +github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI= +github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= 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-exec v0.17.2 h1:EU7i3Fh7vDUI9nNRdMATCEfnm9axzTnad8zszYZ73Go= -github.com/hashicorp/terraform-exec v0.17.2/go.mod h1:tuIbsL2l4MlwwIZx9HPM+LOV9vVyEfBYu2GsO1uH3/8= -github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e17dKDpqV7s= -github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM= -github.com/hashicorp/terraform-plugin-framework v0.10.0 h1:LGYcnvNdVaZA1ZHe53BHLVjaaGs7HTiq6+9Js29stL4= -github.com/hashicorp/terraform-plugin-framework v0.10.0/go.mod h1:CK7Opzukfu/2CPJs+HzUdfHrFlp+ZIQeSxjF0x8k464= -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-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.21.0 h1:eIJjFlI4k6BMso6Wq/bq56U0RukXc4JbwJJ8Oze2/tg= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.21.0/go.mod h1:mYPs/uchNcBq7AclQv9QUtSf9iNcfp1Ag21jqTlDf2M= -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 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0= -github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= +github.com/hashicorp/terraform-exec v0.19.0 h1:FpqZ6n50Tk95mItTSS9BjeOVUb4eg81SpgVtZNNtFSM= +github.com/hashicorp/terraform-exec v0.19.0/go.mod h1:tbxUpe3JKruE9Cuf65mycSIT8KiNPZ0FkuTE3H4urQg= +github.com/hashicorp/terraform-json v0.18.0 h1:pCjgJEqqDESv4y0Tzdqfxr/edOIGkjs8keY42xfNBwU= +github.com/hashicorp/terraform-json v0.18.0/go.mod h1:qdeBs11ovMzo5puhrRibdD6d2Dq6TyE/28JiU4tIQxk= +github.com/hashicorp/terraform-plugin-docs v0.16.0 h1:UmxFr3AScl6Wged84jndJIfFccGyBZn52KtMNsS12dI= +github.com/hashicorp/terraform-plugin-docs v0.16.0/go.mod h1:M3ZrlKBJAbPMtNOPwHicGi1c+hZUh7/g0ifT/z7TVfA= +github.com/hashicorp/terraform-plugin-framework v1.4.2 h1:P7a7VP1GZbjc4rv921Xy5OckzhoiO3ig6SGxwelD2sI= +github.com/hashicorp/terraform-plugin-framework v1.4.2/go.mod h1:GWl3InPFZi2wVQmdVnINPKys09s9mLmTZr95/ngLnbY= +github.com/hashicorp/terraform-plugin-go v0.19.1 h1:lf/jTGTeELcz5IIbn/94mJdmnTjRYm6S6ct/JqCSr50= +github.com/hashicorp/terraform-plugin-go v0.19.1/go.mod h1:5NMIS+DXkfacX6o5HCpswda5yjkSYfKzn1Nfl9l+qRs= +github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= +github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.30.0 h1:X7vB6vn5tON2b49ILa4W7mFAsndeqJ7bZFOGbVO+0Cc= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.30.0/go.mod h1:ydFcxbdj6klCqYEPkPvdvFKiNGKZLUs+896ODUXCyao= +github.com/hashicorp/terraform-plugin-testing v1.6.0 h1:Wsnfh+7XSVRfwcr2jZYHsnLOnZl7UeaOBvsx6dl/608= +github.com/hashicorp/terraform-plugin-testing v1.6.0/go.mod h1:cJGG0/8j9XhHaJZRC+0sXFI4uzqQZ9Az4vh6C4GJpFE= +github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI= +github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM= +github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= +github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= +github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= -github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= -github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= -github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 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/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 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.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng= +github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 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/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.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 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.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= 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/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= +github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/spbsoluble/go-pkcs12 v0.3.1 h1:3DWrjdP3HOeYW6aTUSO9pqqAgRL8VKZLqvD5PGkLVMo= -github.com/spbsoluble/go-pkcs12 v0.3.1/go.mod h1:MX7DY37hx8xHKEMuJ16EMaVT8sT+4KPqK4gTTLFGcH0= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/skeema/knownhosts v1.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM= +github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 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 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY= -github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= -github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= -github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= -github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= -github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= -github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0= -github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= -github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= -go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 h1:CCriYyAfq1Br1aIYettdHZTy8mBTIPo7We18TuO/bak= -go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= -golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +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= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zclconf/go-cty v1.14.1 h1:t9fyA35fwjjUMcmL5hLER+e/rEPqrbCK1/OSE4SI9KA= +github.com/zclconf/go-cty v1.14.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -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-20180826012351-8a410e7b638d/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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 h1:EDuYyU/MkFXllv9QF9819VlI9a4tzGuCbhG0ExK9o1U= +golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 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.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200711021454-869866162049 h1:YFTFpQhgvrLrmxtiIncJxFXeCyq84ixuKWVCaCAi9Oc= -google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= -google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +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/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= 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.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.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/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/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= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= +software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/internal/provider/v3/command/certificate_helpers.go b/internal/provider/v3/command/certificate_helpers.go new file mode 100644 index 00000000..8434372c --- /dev/null +++ b/internal/provider/v3/command/certificate_helpers.go @@ -0,0 +1,419 @@ +package command + +import ( + "context" + "crypto/ecdsa" + rsa2 "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/json" + "encoding/pem" + "fmt" + kfc "github.com/Keyfactor/keyfactor-go-client-sdk/v2/api/command" + "github.com/hashicorp/terraform-plugin-log/tflog" + "io" + "math/rand" + "net/http" + "software.sslmate.com/src/go-pkcs12" + "sort" + "strings" +) + +var ( + lowerCharSet = "abcdedfghijklmnopqrst" + upperCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + specialCharSet = "!@#$%&*" + numberSet = "0123456789" + allCharSet = lowerCharSet + upperCharSet + specialCharSet + numberSet +) + +func generatePassword(passwordLength, minSpecialChar, minNum, minUpperCase int) string { + var password strings.Builder + + //Set special character + for i := 0; i < minSpecialChar; i++ { + random := rand.Intn(len(specialCharSet)) + password.WriteString(string(specialCharSet[random])) + } + + //Set numeric + for i := 0; i < minNum; i++ { + random := rand.Intn(len(numberSet)) + password.WriteString(string(numberSet[random])) + } + + //Set uppercase + for i := 0; i < minUpperCase; i++ { + random := rand.Intn(len(upperCharSet)) + password.WriteString(string(upperCharSet[random])) + } + + remainingLength := passwordLength - minSpecialChar - minNum - minUpperCase + for i := 0; i < remainingLength; i++ { + random := rand.Intn(len(allCharSet)) + password.WriteString(string(allCharSet[random])) + } + inRune := []rune(password.String()) + rand.Shuffle(len(inRune), func(i, j int) { + inRune[i], inRune[j] = inRune[j], inRune[i] + }) + return string(inRune) +} + +//func flattenMetadata(metadata interface{}) types.Map { +// data := make(map[string]string) +// if metadata != nil { +// for k, v := range metadata.(map[string]interface{}) { +// data[k] = v.(string) +// } +// } +// +// result := types.Map{ +// Elems: map[string]attr.Value{}, +// ElemType: types.StringType, +// } +// for k, v := range data { +// result.Elems[k] = types.String{Value: v} +// } +// return result +//} + +func mapSanIDToName(sanID int) string { + switch sanID { + case 0: + return "Other Name" + case 1: + return "RFC 822 Name" + case 2: + return "DNS Name" + case 3: + return "X400 Address" + case 4: + return "Directory Name" + case 5: + return "Ediparty Name" + case 6: + return "Uniform Resource Identifier" + case 7: + return "IP Address" + case 8: + return "Registered Id" + case 100: + return "MS_NTPrincipalName" + case 101: + return "MS_NTDSReplication" + } + return "" +} + +func ParseCertificateBytes(certBytes *string) (string, []string, []string, []string, error) { + // Parse the PEM block containing the certificate + + //convert string to []byte + decoded, dErr := base64.StdEncoding.DecodeString(*certBytes) + block := pem.Block{ + Type: "CERTIFICATE", + Bytes: decoded, + } + if dErr != nil { + return "", nil, nil, nil, fmt.Errorf("failed to decode PEM block containing certificate: %w", dErr) + } + + pemString := string(pem.EncodeToMemory(&block)) + tflog.Trace(context.Background(), fmt.Sprintf("PEM String: %v", pemString)) + + //// Parse the X.509 certificate + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return "", nil, nil, nil, fmt.Errorf("failed to parse certificate: %w", err) + } + + // Extract IP SANs + ipSANs := make([]string, 0) + for _, ip := range cert.IPAddresses { + ipSANs = append(ipSANs, ip.String()) + } + + // Extract DNS SANs + dnsSANs := cert.DNSNames + + // Extract URI SANs + uriSANs := make([]string, 0) + for _, uri := range cert.URIs { + uriSANs = append(uriSANs, uri.String()) + } + + // Sort the SANs + sort.Strings(ipSANs) + sort.Strings(dnsSANs) + sort.Strings(uriSANs) + + return pemString, ipSANs, dnsSANs, uriSANs, nil +} + +func recoverPrivateKey(ctx context.Context, client *kfc.APIClient, id int64, thumbPrint string, sn string, dn string, password string, collectionId *int32) (interface{}, *x509.Certificate, []*x509.Certificate, error) { + ctx = tflog.SetField(ctx, "CertId", id) + tflog.Debug(ctx, fmt.Sprintf("Calling POST %s/Certificates/Recover", client.GetConfig().Host)) + + if password == "" { + tflog.Debug(ctx, "Password is required to recover private key and no password was supplied; using auto generated password.") + password = generatePassword(DefaultPasswdLength, DefaultMinSpecialChar, DefaultMinNum, DefaultMinUpperCase) + } + tflog.Trace(ctx, fmt.Sprintf("PFX Password: %s", password)) + + validInput := false + if id != 0 { + validInput = true + } else if thumbPrint != "" { + validInput = true + } else if sn != "" && dn != "" { + validInput = true + } + tflog.SetField(ctx, "valid_input", validInput) + + id32 := int32(id) + + if !validInput { + return nil, nil, nil, fmt.Errorf("certID, thumbprint, or serial number AND issuer DN required to dowload certificate") + } + + issuerDn := kfc.NullableString{} + if dn != "" { + issuerDn.Set(&dn) + } + + request := kfc.ModelsCertificateRecoveryRequest{ + Password: password, + CertID: &id32, + SerialNumber: &sn, + IssuerDN: issuerDn, + Thumbprint: &thumbPrint, + IncludeChain: convertBoolToPtr(true), + AdditionalProperties: nil, + } + + tflog.Info(ctx, "Attempting to recover private key from Keyfactor Command") + cReq := client.CertificateApi.CertificateRecoverCertificateAsync(ctx). + Rq(request). + XCertificateformat("PFX") + + if collectionId != nil { + cReq.CollectionId(*collectionId) + } + + certsResp, httpResp, respErr := cReq. + Execute() + + if respErr != nil { + defer httpResp.Body.Close() + } + //logCommandAPIResponse(ctx, httpResp, respErr) // this is commented out because you can't read the body twice + + if httpResp.StatusCode == http.StatusOK && (certsResp == nil) { + tflog.Warn(ctx, "Keyfactor SDK client returned a nil object and a 200 status code. This is likely a bug in SDK.") + tflog.Info(ctx, "Attempting to parse HTTP response body as JSON") + bodyBytes, err := io.ReadAll(httpResp.Body) + bodyString := string(bodyBytes) + if err != nil { + tflog.Error(ctx, fmt.Sprintf("Unable to read HTTP response body: %v", err)) + return nil, nil, nil, err + } + tflog.Debug(ctx, fmt.Sprintf("Response body: %s", bodyString)) + + var apiRespObj []kfc.ModelsCertificateRecoveryRequest // TODO: Remove this stuff + var genericObj []map[string]interface{} + gErr := json.Unmarshal(bodyBytes, &genericObj) + if gErr != nil { + tflog.Error(ctx, fmt.Sprintf("Unable to unmarshal JSON response body: %v", gErr)) + } + aErr := json.Unmarshal(bodyBytes, &apiRespObj) + if aErr != nil { + tflog.Error(ctx, fmt.Sprintf("Unable to unmarshal JSON response body: %v", aErr)) + return nil, nil, nil, aErr + } + } else { + if httpResp.StatusCode != http.StatusOK { + return nil, nil, nil, fmt.Errorf("failed to recover certificate: %v", respErr) + } + + pfxString := certsResp.PFX + + tflog.Trace(ctx, fmt.Sprintf("PFX String: %v", pfxString)) + tflog.Debug(ctx, "Decoding PFX to bytes") + pfxDer, dErr := base64.StdEncoding.DecodeString(*certsResp.PFX) + if dErr != nil { + tflog.Error(ctx, fmt.Sprintf("Private key decode error: %s", dErr.Error())) + } + + pkInterface, leaf, chain, err := pkcs12.DecodeChain(pfxDer, password) + if err != nil { + return nil, nil, nil, err + } + var pk string + + rsa, ok := pkInterface.(*rsa2.PrivateKey) + if ok { + tflog.Info(ctx, "Recovered RSA private key from Keyfactor Command:") + buf := x509.MarshalPKCS1PrivateKey(rsa) + if len(buf) > 0 { + pk = string(pem.EncodeToMemory(&pem.Block{ + Bytes: buf, + Type: "RSA PRIVATE KEY", + })) + tflog.Trace(ctx, pk) + } else { + tflog.Warn(ctx, "Empty private key recovered from Keyfactor kfc.") + } + } else { + tflog.Info(ctx, "Attempting ECC private key recovery") + ecc, ok := pkInterface.(*ecdsa.PrivateKey) + if ok { + // We don't really care about the error here. An error just means that the key will be blank which isn't a + // reason to fail + tflog.Info(ctx, "Recovered ECC private key from Keyfactor Command") + buf, _ := x509.MarshalECPrivateKey(ecc) + if len(buf) > 0 { + pk = string(pem.EncodeToMemory(&pem.Block{ + Bytes: buf, + Type: "EC PRIVATE KEY", + })) + tflog.Trace(ctx, pk) + } else { + tflog.Warn(ctx, "Empty private key returned from Keyfactor kfc.") + } + } + } + + return pk, leaf, chain, nil + } + + return "", nil, nil, fmt.Errorf("failed to recover private key for certificate") +} + +func readCertificateById(ctx context.Context, cID int, client *kfc.APIClient, collectionID int64) (*kfc.ModelsCertificateRetrievalResponse, *http.Response, error) { + ctx = tflog.SetField(ctx, "certificate_id", cID) + tflog.Debug(ctx, fmt.Sprintf("Calling GET %s/Certificate/%d", client.GetConfig().Host, cID)) + req := client.CertificateApi.CertificateGetCertificate(ctx, int32(cID)). + IncludeMetadata(true). + IncludeLocations(true) + + if collectionID > 0 { + ctx = tflog.SetField(ctx, "collection_id", collectionID) + tflog.Debug(ctx, "Adding collection_id to request") + req.CollectionId(int32(collectionID)) + } + clientResp, httpResp, respErr := req.Execute() + logCommandAPIResponse(ctx, httpResp, respErr) + + if httpResp.StatusCode == http.StatusNotFound { + tflog.Warn(ctx, fmt.Sprintf("Unable to find certificate %d using Keyfactor Command certificate Id. Attempting to search as serial number.", cID)) + clientResp, httpResp, respErr = lookupCertificate(ctx, CertificateSNFieldName, fmt.Sprintf("%d", cID), client, collectionID) + } + + var ( + detailedClientResp *kfc.ModelsCertificateRetrievalResponse + detailedHttpResp *http.Response + detailedRespErr error + ) + tp := clientResp.Thumbprint + if tp != nil { + detailedClientResp, detailedHttpResp, detailedRespErr = lookupCertificate(ctx, CertificateThumbprintFieldName, tp, client, collectionID) + } else { + tflog.Warn(ctx, fmt.Sprintf("Thumbprint for certificate %d is nil. Attempting to search as serial number.", cID)) + sn := clientResp.SerialNumber + if sn == nil { + tflog.Warn(ctx, fmt.Sprintf("Serial number for certificate %d is nil. Attempting to search as Issuer DN.", cID)) + dn := clientResp.IssuedDN + if dn.IsSet() { + detailedClientResp, detailedHttpResp, detailedRespErr = lookupCertificate(ctx, CertificateDNFieldName, dn, client, collectionID) + } else { + tflog.Warn(ctx, fmt.Sprintf("Unable to lookup locations, private key and metadata for %d", cID)) + } + } else { + detailedClientResp, detailedHttpResp, detailedRespErr = lookupCertificate(ctx, CertificateSNFieldName, sn, client, collectionID) + } + } + if detailedClientResp != nil && detailedRespErr == nil { + //compare the IDs to make sure we have the same cert + if *detailedClientResp.Id != *clientResp.Id { + tflog.Warn(ctx, fmt.Sprintf("Certificate ID for certificate %d does not match certificate ID for certificate %d", detailedClientResp.Id, clientResp.Id)) + } else { + return detailedClientResp, detailedHttpResp, detailedRespErr + } + } + return clientResp, httpResp, respErr +} + +func lookupCertificate(ctx context.Context, fieldName string, fieldValue interface{}, client *kfc.APIClient, collectionId int64) (*kfc.ModelsCertificateRetrievalResponse, *http.Response, error) { + var ( + q string + notFoundErr error + ) + + switch fieldValue.(type) { + case string: + q = fmt.Sprintf(`%s -eq "%s"`, fieldName, fieldValue.(string)) + notFoundErr = fmt.Errorf("unable to find certificate %s in Keyfactor Command", fieldValue.(string)) + case *string: + q = fmt.Sprintf(`%s -eq "%s"`, fieldName, *fieldValue.(*string)) + notFoundErr = fmt.Errorf("unable to find certificate %s in Keyfactor Command", *fieldValue.(*string)) + case *kfc.NullableString: + q = fmt.Sprintf(`%s -eq "%s"`, fieldName, *fieldValue.(*kfc.NullableString).Get()) + notFoundErr = fmt.Errorf("unable to find certificate %s in Keyfactor Command", *fieldValue.(*kfc.NullableString).Get()) + case int, int32, int64: + q = fmt.Sprintf(`%s -eq %d`, fieldName, fieldValue) + notFoundErr = fmt.Errorf("unable to find certificate %d in Keyfactor Command", fieldValue) + case *int, *int32, *int64: + q = fmt.Sprintf(`%s -eq %d`, fieldName, *fieldValue.(*int)) + notFoundErr = fmt.Errorf("unable to find certificate %d in Keyfactor Command", *fieldValue.(*int)) + case *kfc.NullableInt32, *kfc.NullableInt64: + q = fmt.Sprintf(`%s -eq %d`, fieldName, fieldValue.(*kfc.NullableInt32).Get()) + notFoundErr = fmt.Errorf("unable to find certificate %d in Keyfactor Command", fieldValue.(*kfc.NullableInt32).Get()) + } + ctx = tflog.SetField(ctx, fieldName, fieldValue) + tflog.Info(ctx, fmt.Sprintf("Looking up cert by '%s'", fieldName)) + ctx = tflog.SetField(ctx, "query_string", q) + tflog.Debug(ctx, fmt.Sprintf("Calling GET %s/Certificate?QueryString=%s", client.GetConfig().Host, q)) + req := client.CertificateApi.CertificateQueryCertificates(ctx). + IncludeMetadata(true). + IncludeLocations(true). + IncludeHasPrivateKey(true). + PqQueryString(q) + if collectionId > 0 { + ctx = tflog.SetField(ctx, "collection_id", collectionId) + tflog.Debug(ctx, fmt.Sprintf("Adding collection_id to request")) + req.CollectionId(int32(collectionId)) + } + certsResp, httpResp, respErr := req.Execute() + + logCommandAPIResponse(ctx, httpResp, respErr) + + if len(certsResp) > 0 { + // find the newest cert by ImportDate + var newestCert kfc.ModelsCertificateRetrievalResponse + for _, cert := range certsResp { // Check if newestCert is empty, if it is set it to the first cert in the list + if newestCert.ImportDate == nil { + tflog.Debug(ctx, fmt.Sprintf("Setting newestCert to '%v(%v)'", cert.IssuedDN, cert.Thumbprint)) + newestCert = cert + continue + } + + if cert.ImportDate == nil || newestCert.ImportDate == nil { + tflog.Error(ctx, fmt.Sprintf("Unable to compare %v(%v) and %v(%v)", cert.IssuedDN, cert.Thumbprint, newestCert.IssuedDN, newestCert.Thumbprint)) + continue + } + + if cert.ImportDate.After(*newestCert.ImportDate) { + tflog.Info(ctx, fmt.Sprintf("Multiple certs found, using most recently issued cert for %v", fieldValue)) + tflog.Debug(ctx, fmt.Sprintf("Setting newestCert to '%v(%v)'", cert.IssuedDN, cert.Thumbprint)) + newestCert = cert + } + } + return &newestCert, httpResp, respErr + } else if len(certsResp) <= 0 { + tflog.Error(ctx, notFoundErr.Error()) + return nil, httpResp, notFoundErr + } + tflog.Error(ctx, fmt.Sprintf("Unable to find certificate by '%s'", fieldName)) + return nil, httpResp, respErr // no cert was found and an API error was returned +} diff --git a/internal/provider/v3/command/constants.go b/internal/provider/v3/command/constants.go new file mode 100644 index 00000000..494b84d4 --- /dev/null +++ b/internal/provider/v3/command/constants.go @@ -0,0 +1,46 @@ +package command + +const ( + InvalidConfigError = "Invalid provider configuration." + InvalidUsernameError = "`username` and `client_id` cannot both be empty" + InvalidPasswordError = "`password` and `client_secret` cannot both be empty" + InvalidDomainError = "`domain` was not provided and could not be determined from `username`" + InvalidHostNameError = "`hostname` was not provided and could not be determined from `host`" + DefaultPasswdLength = 18 + DefaultMinSpecialChar = 2 + DefaultMinNum = 2 + DefaultMinUpperCase = 2 + CertificateSNFieldName = "SerialNumber" + CertificateThumbprintFieldName = "Thumbprint" + CertificateCNFieldName = "IssuedCN" + CertificateDNFieldName = "IssuedDN" + CertificateThumbprintLength = 40 + APICertStateIsCA = "CertificateAuthority (6)" + TestCAName = "DC-CA.Command.local\\CommandCA1" + ApprovedAgentStatus = 2 + AgentNotFound = "unable to find agent in Keyfactor Command" +) + +var ENVIRONMENTAL_VARS = map[string]string{ + "KEYFACTOR_HOSTNAME": "hostname", + "KEYFACTOR_APPKEY": "appkey", + "KEYFACTOR_PASSWORD": "password", + "KEYFACTOR_CERTIFICATE": "certificate", + "KEYFACTOR_CLIENT_ID": "client_id", + "KEYFACTOR_CLIENT_SECRET": "client_secret", + "KEYFACTOR_AUTH_CONFIG": "auth_config", + "COMMAND_HOSTNAME": "hostname", + "COMMAND_APPKEY": "appkey", + "COMMAND_PASSWORD": "password", + "COMMAND_CERTIFICATE": "certificate", + "COMMAND_CLIENT_ID": "client_id", + "COMMAND_CLIENT_SECRET": "client_secret", + "COMMAND_AUTH_CONFIG": "auth_config", + "KEYFACTOR_COMMAND_HOSTNAME": "hostname", + "KEYFACTOR_COMMAND_APPKEY": "appkey", + "KEYFACTOR_COMMAND_PASSWORD": "password", + "KEYFACTOR_COMMAND_CERTIFICATE": "certificate", + "KEYFACTOR_COMMAND_CLIENT_ID": "client_id", + "KEYFACTOR_COMMAND_CLIENT_SECRET": "client_secret", + "KEYFACTOR_COMMAND_AUTH_CONFIG": "auth_config", +} diff --git a/internal/provider/v3/command/data_source_keyfactor_agent.go b/internal/provider/v3/command/data_source_keyfactor_agent.go new file mode 100644 index 00000000..ea60547a --- /dev/null +++ b/internal/provider/v3/command/data_source_keyfactor_agent.go @@ -0,0 +1,294 @@ +package command + +import ( + "context" + "fmt" + kfc "github.com/Keyfactor/keyfactor-go-client-sdk/v2/api/command" + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "net/http" +) + +var _ datasource.DataSource = &AgentDataSource{} + +func NewAgentDataSource() datasource.DataSource { + return &AgentDataSource{} +} + +// AgentDataSource defines the data source implementation. +type AgentDataSource struct { + provider *Provider + client *kfc.APIClient +} + +func (d *AgentDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = "keyfactor_agent" +} + +func (d *AgentDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "agent_id": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the GUID of the orchestrator.", + MarkdownDescription: "A string indicating the GUID of the orchestrator.", + }, + "agent_identifier": schema.StringAttribute{ + Required: true, + Description: "A string indicating the GUID or client machine name of the orchestrator.", + MarkdownDescription: "A string indicating the GUID or client machine name of the orchestrator.", + }, + "agent_platform": schema.Int64Attribute{ + Computed: true, + Description: "An integer indicating the platform for the orchestrator.\n- 0 = Unknown\n- 1 = Keyfactor Windows Orchestrator\n- 2 = Keyfactor Java Agent\n- 3 = Keyfactor Mac Auto-Enrollment Agent\n- 4 = Keyfactor Android Agent\n- 5 = Keyfactor Native Agent\n- 6 = Keyfactor Bash Orchestrator\n- 7 = Keyfactor Universal Orchestrator\n", + MarkdownDescription: "An integer indicating the platform for the orchestrator.\n- 0 = Unknown\n- 1 = Keyfactor Windows Orchestrator\n- 2 = Keyfactor Java Agent\n- 3 = Keyfactor Mac Auto-Enrollment Agent\n- 4 = Keyfactor Android Agent\n- 5 = Keyfactor Native Agent\n- 6 = Keyfactor Bash Orchestrator\n- 7 = Keyfactor Universal Orchestrator\n", + }, + "auth_certificate_reenrollment": schema.StringAttribute{ + Computed: true, + Description: "An integer indicating the value of the orchestrator certificate reenrollment request or require status.\nPossible values:\n- 0 = None—Unset the value so that the orchestrator will not request a new client authentication certificate (based on this value).\n- 1 = Requested—The orchestrator will request a new client authentication certificate when it next registers for a session. Orchestrator activity will be allowed to continue as usual.\n- 2 = Required—The orchestrator will request a new client authentication certificate when it next registers for a session. A new session will not be granted and orchestrator activity will not be allowed to continue until the orchestrator acquires a new certificate.\n", + MarkdownDescription: "An integer indicating the value of the orchestrator certificate reenrollment request or require status.\nPossible values:\n- 0 = None—Unset the value so that the orchestrator will not request a new client authentication certificate (based on this value).\n- 1 = Requested—The orchestrator will request a new client authentication certificate when it next registers for a session. Orchestrator activity will be allowed to continue as usual.\n- 2 = Required—The orchestrator will request a new client authentication certificate when it next registers for a session. A new session will not be granted and orchestrator activity will not be allowed to continue until the orchestrator acquires a new certificate.\n", + }, + "blueprint": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the name of the blueprint associated with the orchestrator.", + MarkdownDescription: "A string indicating the name of the blueprint associated with the orchestrator.", + }, + "capabilities": schema.ListAttribute{ + ElementType: types.StringType, + Computed: true, + Description: "An array of strings indicating the capabilities reported by the orchestrator. These may be built-in or custom capabilities.", + MarkdownDescription: "An array of strings indicating the capabilities reported by the orchestrator. These may be built-in or custom capabilities.", + }, + "client_machine": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the client machine on which the orchestrator is installed.", + MarkdownDescription: "A string indicating the client machine on which the orchestrator is installed.", + }, + "id": schema.StringAttribute{ + //Required: true, + Computed: true, + Description: "A string indicating the GUID of the orchestrator.", + MarkdownDescription: "A string indicating the GUID of the orchestrator.", + }, + "last_error_code": schema.Int64Attribute{ + Computed: true, + Description: "An integer indicating the last error code, if any, reported from the orchestrator when trying to register a session. This code is cleared on successful session registration.", + MarkdownDescription: "An integer indicating the last error code, if any, reported from the orchestrator when trying to register a session. This code is cleared on successful session registration.", + }, + "last_error_message": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the last error message, if any, reported from the orchestrator when trying to register a session. This message is cleared on successful session registration.", + MarkdownDescription: "A string indicating the last error message, if any, reported from the orchestrator when trying to register a session. This message is cleared on successful session registration.", + }, + "last_seen": schema.StringAttribute{ + Computed: true, + Description: "The time, in UTC, at which the orchestrator last contacted Keyfactor Command.", + MarkdownDescription: "The time, in UTC, at which the orchestrator last contacted Keyfactor Command.", + }, + "last_thumbprint_used": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the thumbprint of the certificate that the orchestrator most recently used for client certificate authentication. In most cases, this will match the Thumbprint.", + MarkdownDescription: "A string indicating the thumbprint of the certificate that the orchestrator most recently used for client certificate authentication. In most cases, this will match the Thumbprint.", + }, + "legacy_thumbprint": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the thumbprint of the certificate previously used by the orchestrator for client certificate authentication before a certificate renewal operation took place (rotating the current thumbprint into the legacy thumbprint). The legacy thumbprint is cleared once the orchestrator successfully registers with the new thumbprint.", + MarkdownDescription: "A string indicating the thumbprint of the certificate previously used by the orchestrator for client certificate authentication before a certificate renewal operation took place (rotating the current thumbprint into the legacy thumbprint). The legacy thumbprint is cleared once the orchestrator successfully registers with the new thumbprint.", + }, + "status": schema.Int64Attribute{ + Computed: true, + Description: "An integer indicating the orchestrator status:\n- 1 = New\n- 2 = Approved\n- 3 = Disapproved\n", + MarkdownDescription: "An integer indicating the orchestrator status:\n- 1 = New\n- 2 = Approved\n- 3 = Disapproved\n", + }, + "thumbprint": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the thumbprint of the certificate that Keyfactor Command is expecting the orchestrator to use for client certificate authentication.", + MarkdownDescription: "A string indicating the thumbprint of the certificate that Keyfactor Command is expecting the orchestrator to use for client certificate authentication.", + }, + "username": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the Active Directory user or service account the orchestrator is using to connect to Keyfactor Command.", + MarkdownDescription: "A string indicating the Active Directory user or service account the orchestrator is using to connect to Keyfactor Command.", + }, + "version": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the version of the orchestrator.", + MarkdownDescription: "A string indicating the version of the orchestrator.", + }, + }, + } +} + +type AgentModel struct { + AgentId types.String `tfsdk:"agent_id"` + AgentIdentifier types.String `tfsdk:"agent_identifier"` + AgentPlatform types.Int64 `tfsdk:"agent_platform"` + AuthCertificateReenrollment types.String `tfsdk:"auth_certificate_reenrollment"` + Blueprint types.String `tfsdk:"blueprint"` + Capabilities types.List `tfsdk:"capabilities"` + ClientMachine types.String `tfsdk:"client_machine"` + Id types.String `tfsdk:"id"` + LastErrorCode types.Int64 `tfsdk:"last_error_code"` + LastErrorMessage types.String `tfsdk:"last_error_message"` + LastSeen types.String `tfsdk:"last_seen"` + LastThumbprintUsed types.String `tfsdk:"last_thumbprint_used"` + LegacyThumbprint types.String `tfsdk:"legacy_thumbprint"` + Status types.Int64 `tfsdk:"status"` + Thumbprint types.String `tfsdk:"thumbprint"` + Username types.String `tfsdk:"username"` + Version types.String `tfsdk:"version"` +} + +func (d *AgentDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*kfc.APIClient) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *kfc.APIClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + d.client = client +} + +func (d *AgentDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data AgentModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + if data.AgentIdentifier.IsNull() || data.AgentIdentifier.ValueString() == "" { + resp.Diagnostics.AddError( + "Invalid Agent Identifier", + "agent_identifier must not be null or empty, please provide either a GUID or ClientMachine name.", + ) + return + } + + ctx = tflog.SetField(ctx, "agent_identifier", data.AgentIdentifier.ValueString()) + tflog.Info(ctx, "Read called on agent data source") + + agentsResp, httpResp, httpRespErr := d.lookupAgent(ctx, data.AgentIdentifier.ValueString()) + + if httpRespErr != nil { + resp.Diagnostics.AddError( + "Agent Read Error", + fmt.Sprintf("Error querying Keyfactor Command for agent '%s': %s", data.AgentIdentifier.ValueString(), httpRespErr.Error()), + ) + return + } else if httpResp != nil && httpResp.StatusCode != 200 { + resp.Diagnostics.AddError( + "Agent Read Error", + fmt.Sprintf("Error querying Keyfactor Command for agent '%s': %s", data.AgentIdentifier.String(), httpResp.Status), + ) + return + } + + if agentsResp == nil { + resp.Diagnostics.AddError( + "Agent Not Found", + fmt.Sprintf("No agent found with identifier '%s'", data.AgentIdentifier.String()), + ) + return + } + + tflog.Info(ctx, "Setting agent data source attributes") + data.AgentId = types.StringValue(*agentsResp.AgentId) + data.AgentPlatform = types.Int64Value(convertInt64Ptr(agentsResp.AgentPlatform)) + data.AuthCertificateReenrollment = types.StringValue(*agentsResp.AuthCertificateReenrollment) + data.Blueprint = types.StringValue(convertStringPtr(agentsResp.Blueprint)) + data.Capabilities, _ = convertToTerraformList(agentsResp.Capabilities) + data.ClientMachine = types.StringValue(*agentsResp.ClientMachine) + data.Id = types.StringValue(*agentsResp.AgentId) + //data.LastErrorCode = types.Int64Value(convertInt64Ptr(int(*agentsResp.LastErrorCode))) + data.LastErrorMessage = types.StringValue(convertStringPtr(agentsResp.LastErrorMessage)) + data.LastSeen = types.StringValue(convertTimeToStringPtr(agentsResp.LastSeen)) + data.LastThumbprintUsed = types.StringValue(convertStringPtr(agentsResp.LastThumbprintUsed)) + data.LegacyThumbprint = types.StringValue(*agentsResp.LegacyThumbprint) + data.Status = types.Int64Value(convertInt64Ptr(agentsResp.Status)) + data.Thumbprint = types.StringValue(*agentsResp.Thumbprint) + data.Username = types.StringValue(*agentsResp.Username) + data.Version = types.StringValue(*agentsResp.Version) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (d *AgentDataSource) lookupAgent(ctx context.Context, identifier string) (*kfc.KeyfactorApiModelsOrchestratorsAgentResponse, *http.Response, error) { + // Check if string is a GUID + if _, err := uuid.Parse(identifier); err != nil { + // Not a GUID, so look up by client machine name + tflog.Info(ctx, fmt.Sprintf("Agent identifier '%s' is not a GUID. Looking up by client machine name.", identifier)) + agents, httpResp, httpRespErr := d.client.AgentApi.AgentGetAgents(ctx). + PqQueryString(fmt.Sprintf("ClientMachine -eq \"%s\"", identifier)). + Execute() + if httpRespErr != nil { + return nil, httpResp, httpRespErr + } else if len(agents) == 0 { + return nil, httpResp, httpRespErr + } else if len(agents) >= 1 { + //iterate through agents and find the most recently seen + mostRecentAgent := agents[0] + for _, a := range agents { + // 1 = New + // 2 = Approved + // 3 = Disapproved + if *a.Status != 2 { + tflog.Debug(ctx, fmt.Sprintf("Agent '%s' is not approved. Skipping.", *a.AgentId)) + continue + } + + if mostRecentAgent.LastSeen == nil { + mostRecentAgent = a + continue + } else if a.LastSeen == nil { + continue + } else if mostRecentAgent.LastSeen.After(*a.LastSeen) { + mostRecentAgent = a + } + } + return &mostRecentAgent, httpResp, httpRespErr + } + } + + // Lookup agent by GUID + tflog.Info(ctx, fmt.Sprintf("Agent identifier '%s' is a GUID. Looking up by GUID.", identifier)) + agent, httpResp, httpRespErr := d.client.AgentApi.AgentGetAgentDetail(ctx, identifier).Execute() + if httpRespErr != nil { + tflog.Error(ctx, fmt.Sprintf("error querying Keyfactor Command for agent '%s': %s", identifier, httpRespErr.Error())) + return nil, httpResp, httpRespErr + } else if httpResp.StatusCode != 200 { + apiErr := fmt.Errorf("error '%s' querying Keyfactor Command for agent '%s'", httpResp.Status, identifier) + return nil, httpResp, apiErr + } + return agent, httpResp, httpRespErr +} + +func (d *AgentDataSource) lookupRandomAgent(ctx context.Context) (*kfc.KeyfactorApiModelsOrchestratorsAgentResponse, *http.Response, error) { + agentResp, httpResp, httpErr := d.client.AgentApi.AgentGetAgents(ctx).Execute() + if httpErr != nil { + return nil, httpResp, httpErr + } else if agentResp != nil { + for _, agent := range agentResp { + if agent.AgentId != nil && *agent.AgentId != "" && agent.Status != nil && *agent.Status == ApprovedAgentStatus { + return &agent, httpResp, httpErr + } + } + } + return nil, httpResp, fmt.Errorf(AgentNotFound) +} diff --git a/internal/provider/v3/command/data_source_keyfactor_agent_test.go b/internal/provider/v3/command/data_source_keyfactor_agent_test.go new file mode 100644 index 00000000..94942324 --- /dev/null +++ b/internal/provider/v3/command/data_source_keyfactor_agent_test.go @@ -0,0 +1,71 @@ +package command + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "os" + "testing" +) + +func TestAccKeyfactorAgentDataSource(t *testing.T) { + var resourceName = fmt.Sprintf("data.%s.test", "keyfactor_agent") + var aID = os.Getenv("TEST_AGENT_ID") + var aCN = os.Getenv("TEST_AGENT_CLIENT_MACHINE_NAME") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + // Test lookup by ID + Config: testAccDataSourceKeyfactorAgentBasic(t, aID), + Check: agentTestValidateAll(resourceName, aID), + }, + { + // Test lookup by empty ID + Config: testAccDataSourceKeyfactorAgentBasic(t, ""), + Check: agentTestValidateAll(resourceName, ""), + ExpectError: invalidAgentRequestErrRegex, + }, + { + // Test lookup by client machine name + Config: testAccDataSourceKeyfactorAgentBasic(t, aCN), + Check: agentTestValidateAll(resourceName, aCN), + }, + }, + }) +} + +func testAccDataSourceKeyfactorAgentBasic(t *testing.T, resourceId string) string { + output := fmt.Sprintf(` + data "keyfactor_agent" "test" { + agent_identifier = "%s" + } + `, resourceId) + t.Logf("%s", output) + return output +} + +func agentTestValidateAll(resourceName string, resourceId string) resource.TestCheckFunc { + return resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "agent_id"), + resource.TestCheckResourceAttrSet(resourceName, "agent_identifier"), + resource.TestCheckResourceAttr(resourceName, "agent_identifier", resourceId), + resource.TestCheckResourceAttrSet(resourceName, "agent_platform"), + resource.TestCheckResourceAttrSet(resourceName, "auth_certificate_reenrollment"), + resource.TestCheckResourceAttrSet(resourceName, "blueprint"), + resource.TestCheckResourceAttrSet(resourceName, "capabilities.#"), + resource.TestCheckResourceAttrSet(resourceName, "client_machine"), + resource.TestCheckResourceAttrSet(resourceName, "id"), + //resource.TestCheckResourceAttrSet(resourceName, "last_error_code"), + resource.TestCheckResourceAttrSet(resourceName, "last_error_message"), + resource.TestCheckResourceAttrSet(resourceName, "last_seen"), + resource.TestCheckResourceAttrSet(resourceName, "last_thumbprint_used"), + //resource.TestCheckResourceAttrSet(resourceName, "legacy_thumbprint"), + resource.TestCheckResourceAttrSet(resourceName, "status"), + //resource.TestCheckResourceAttrSet(resourceName, "thumbprint"), + resource.TestCheckResourceAttrSet(resourceName, "username"), + resource.TestCheckResourceAttrSet(resourceName, "version"), + ) +} diff --git a/internal/provider/v3/command/data_source_keyfactor_certificate.go b/internal/provider/v3/command/data_source_keyfactor_certificate.go new file mode 100644 index 00000000..d554ef24 --- /dev/null +++ b/internal/provider/v3/command/data_source_keyfactor_certificate.go @@ -0,0 +1,775 @@ +package command + +import ( + "context" + "crypto/x509" + "encoding/json" + "encoding/pem" + "fmt" + kfc "github.com/Keyfactor/keyfactor-go-client-sdk/v2/api/command" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "net/http" + "strconv" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ datasource.DataSource = &CertificateDataSource{} + +func NewCertificateDataSource() datasource.DataSource { + return &CertificateDataSource{} +} + +// CertificateDataSource defines the data source implementation. +type CertificateDataSource struct { + provider *Provider + client *kfc.APIClient +} + +func (d *CertificateDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = "keyfactor_certificate" +} + +func (d *CertificateDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "Example data source", + + Attributes: map[string]schema.Attribute{ + //"configurable_attribute": schema.StringAttribute{ + // MarkdownDescription: "Example configurable attribute", + // Optional: true, + //}, + "certificate_id": schema.Int64Attribute{ + MarkdownDescription: "This is the unique identifier for the certificate in Keyfactor Command.", + Computed: true, + }, + "identifier": schema.StringAttribute{ + Required: true, + Optional: false, + Computed: false, + Sensitive: false, + Description: "Keyfactor Command certificate identifier. This can be any of the following values: thumbprint, CN, " + + "or Keyfactor Command Certificate ID. If using CN to lookup the last issued certificate, the CN must " + + "be an exact match and if multiple certificates are returned the certificate that was most recently " + + "issued will be returned. ", + MarkdownDescription: "Keyfactor Command certificate identifier. This can be any of the following values: thumbprint, CN, " + + "or Keyfactor Command Certificate ID. If using CN to lookup the last issued certificate, the CN must " + + "be an exact match and if multiple certificates are returned the certificate that was most recently " + + "issued will be returned. ", + DeprecationMessage: "", + Validators: nil, + }, + "thumbprint": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the thumbprint of the certificate.", + }, + "serial_number": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the serial number of the certificate.", + }, + "issued_dn": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the issued distinguished name of the certificate.", + }, + "issued_cn": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the issued common name of the certificate.", + }, + "import_date": schema.StringAttribute{ + Computed: true, + Description: "The date, in UTC, on which the certificate was imported into Keyfactor Command.", + }, + "not_before": schema.StringAttribute{ + Computed: true, + Description: "The date, in UTC, on which the certificate was issued by the certificate authority.", + }, + "not_after": schema.StringAttribute{ + Computed: true, + Description: "The date, in UTC, on which the certificate expires.", + }, + "issuer_dn": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the distinguished name of the certificate authority that issued the certificate.", + }, + "principal_id": schema.Int64Attribute{ + Computed: true, + Description: "An integer indicating the Keyfactor Command reference ID of the principal (UPN) that requested the certificate. Typically, this field is only populated for end user certificates requested through Keyfactor Command (e.g. Mac auto-enrollment certificates). See also PrinicpalName.", + }, + "template_id": schema.Int64Attribute{ + Computed: true, + Description: "An integer indicating the Keyfactor Command reference ID of the template associated with the certificate.", + }, + "cert_state": schema.StringAttribute{ + Computed: true, + Description: "An integer specifying the state of the certificate.", + MarkdownDescription: ` + An integer specifying the state of the certificate. The following values are possible: + | Value | Description | + |-------|-------------| + | 0 | Unknown | + | 1 | Active | + | 2 | Revoked | + | 3 | Denied | + | 4 | Failed | + | 5 | Pending | + | 6 | Certificate Authority | + | 7 | Parent Certificate Authority | + `, + }, + "key_bits": schema.Int64Attribute{ + Computed: true, + Description: "An integer specifying the key size in bits.", + }, + "key_type": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the type of key.", + }, + "requester_id": schema.Int64Attribute{ + Computed: true, + Description: "An integer indicating the Keyfactor Command reference ID of the requester (UPN) that requested the certificate. Typically, this field is only populated for end user certificates requested through Keyfactor Command (e.g. Mac auto-enrollment certificates). See also RequesterName.", + }, + "issued_ou": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the issued organizational unit of the certificate.", + }, + "issued_email": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the email address of the certificate.", + }, + "key_usage": schema.Int64Attribute{ + Computed: true, + Description: "An integer indicating the total key usage of the certificate. Key usage is stored in Active Directory as a single value made of a combination of values. ", + MarkdownDescription: ` + An integer indicating the total key usage of the certificate. Key usage is stored in Active Directory as a single value made of a combination of values. The following values are possible: + | Value | Function | Description | + |-------|----------|-------------| + | 0 | None | No key usage | + | 1 | EncipherOnly | The key can be used for encryption only. | + | 2 | CRL Signing | The key can be used to sign a certificate revocation list (CRL). | + | 4 | Key CertSign | The key can be used to sign certificates. | + | 8 | Key Agreement | The key can be used for key agreement. | + | 16 | Data Encipherment | The key can be used for data encryption. | + | 32 | Key Encipherment | The key can be used for key encryption. | + | 64 | Non Repudiation | The key can be used for authentication. | + | 128 | Digital Signature | The key can be used for digital signatures. | + | 32768 | Decipherment Only | The key can be used for decryption only. | + + For example, a value of 160 would represent a key usage of digital signature with key encipherment. A value of 224 would add nonrepudiation to those. + `, + }, + "signing_algorithm": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the signing algorithm of the certificate.", + }, + "revocation_effective_date": schema.StringAttribute{ + Computed: true, + Description: "The date, in UTC, on which the certificate was revoked.", + }, + "revocation_reason": schema.Int64Attribute{ + Computed: true, + Description: "An integer indicating the reason the certificate was revoked.", + MarkdownDescription: ` + An integer indicating the reason the certificate was revoked. The following values are possible: + | Value | Description | + |-------|-------------| + | 0 | Unspecified | + | 1 | Key Compromise | + | 2 | CA Compromise | + | 3 | Affiliation Changed | + | 4 | Superseded | + | 5 | Cessation of Operation | + | 6 | Certificate Hold | + | 999 | Unknown | + `, + }, + "revocation_comment": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the comment associated with the revocation of the certificate.", + }, + "certificate_authority_id": schema.Int64Attribute{ + Computed: true, + Description: "An integer indicating the Keyfactor Command reference ID of the certificate authority that issued the certificate.", + }, + "certificate_authority": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the name of the certificate authority that issued the certificate.", + }, + "certificate_template": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the name of the template associated with the certificate.", + }, + "archived_key": schema.BoolAttribute{ + Computed: true, + Description: "A boolean indicating whether the certificate has been archived.", + }, + "has_private_key": schema.BoolAttribute{ + Computed: true, + Description: "A boolean indicating whether the certificate has a private key.", + }, + "principal_name": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the principal (UPN) that requested the certificate. Typically, this field is only populated for end user certificates requested through Keyfactor Command (e.g. Mac auto-enrollment certificates). See also PrincipalId.", + }, + "command_request_id": schema.Int64Attribute{ + Computed: true, + Description: "An integer indicating the Keyfactor Command reference ID of the certificate request associated with the certificate.", + }, + "requester_name": schema.StringAttribute{ + Computed: true, + Description: "A string containing the name of the identity that requested the certificate.", + }, + "content_bytes": schema.StringAttribute{ + Computed: true, + Description: "A string containing the certificate as bytes.", + }, + "extended_key_usage": schema.ListNestedAttribute{ + Computed: true, + Description: "An array of objects containing the extended key usage of the certificate.", + MarkdownDescription: ` + An array of objects containing the extended key usage of the certificate. Each object contains the following fields: + | Field | Description | + |-------|-------------| + | Id | An integer indicating the Keyfactor Command reference ID of the extended key usage. | + | Oid | A string indicating the object identifier (OID) of the extended key usage. | + | DisplayName | A string indicating the display name of the extended key usage. | + `, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "oid": schema.StringAttribute{ + Description: "A string indicating the object identifier (OID) of the extended key usage.", + Computed: true, + }, + "display_name": schema.StringAttribute{ + Description: "A string indicating the display name of the extended key usage.", + Computed: true, + }, + "id": schema.Int64Attribute{ + Description: "An integer indicating the Keyfactor Command reference ID of the extended key usage.", + Computed: true, + }, + }, + CustomType: nil, + Validators: nil, + }, + }, + "subject_alt_name_element": schema.ObjectAttribute{ + AttributeTypes: map[string]attr.Type{}, + Computed: true, + Description: "An array of objects containing the subject alternative name elements of the certificate.", + MarkdownDescription: ` + An array of objects containing the subject alternative name elements of the certificate. Each object contains the following fields: + | Field | Description | + |-------|-------------| + | Id | An integer indicating the Keyfactor Command reference ID of the subject alternative name element. | + | Value | A string indicating the value of the subject alternative name element. | + | Type | An integer indicating the type of the subject alternative name element. | + | ValueHash | A string indicating the hash of the value of the subject alternative name element. | + `, + }, + "crl_distribution_point": schema.ListNestedAttribute{ + Computed: true, + Description: "An array of objects containing the certificate revocation list (CRL) distribution points of the certificate.", + MarkdownDescription: ` + An array of objects containing the certificate revocation list (CRL) distribution points of the certificate. Each object contains the following fields: + | Field | Description | + |-------|-------------| + | Id | An integer indicating the Keyfactor Command reference ID of the CRL distribution point. | + | Url | A string indicating the URL of the CRL distribution point. | + | UrlHash | A string indicating the hash of the URL of the CRL distribution point. | + `, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.Int64Attribute{ + Computed: true, + Description: "", + }, + "url": schema.StringAttribute{ + Computed: true, + Description: "", + }, + "url_hash": schema.StringAttribute{ + Computed: true, + Description: "", + }, + }, + }, + }, + "locations_count": schema.ListNestedAttribute{ + Computed: true, + Description: "An array of objects containing the certificate locations of the certificate.", + MarkdownDescription: ` + An array of objects containing the certificate locations of the certificate. Each object contains the following fields: + | Field | Description | + |-------|-------------| + | Type | A string indicating the store type of the certificate location. | + | Count | An integer indicating the number of certificates in the certificate location. | + `, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "store_type": schema.StringAttribute{ + Computed: true, + Description: "", + }, + "count": schema.Int64Attribute{ + Computed: true, + Description: "", + }, + }, + }, + }, + "ssl_location": schema.ListNestedAttribute{ + Computed: true, + Description: "An array of objects containing the SSL locations where the certificate is found using SSL discovery.", + MarkdownDescription: ` + An array of objects containing the SSL locations where the certificate is found using SSL discovery. Each object contains the following fields: + | Field | Description | + |-------|-------------| + | StorePath | A string indicating the path of the SSL location. | + | AgentPool | A string indicating the agent pool of the SSL location. | + | IPAddress | A string indicating the IP address of the SSL location. | + | Port | An integer indicating the port of the SSL location. | + | NetworkName | A string indicating the network name of the SSL location. | + `, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "store_path": schema.StringAttribute{ + Computed: true, + Description: "", + }, + "agent_pool": schema.StringAttribute{ + Computed: true, + Description: "", + }, + "ip_address": schema.StringAttribute{ + Computed: true, + Description: "", + }, + "port": schema.Int64Attribute{ + Computed: true, + Description: "", + }, + "network_name": schema.StringAttribute{ + Computed: true, + Description: "", + }, + }, + }, + }, + "location": schema.ObjectAttribute{ + AttributeTypes: map[string]attr.Type{}, + Computed: true, + Description: "An array of objects containing the certificate locations where the certificate is found using certificate store inventorying.", + MarkdownDescription: ` + An array of objects containing the certificate locations where the certificate is found using certificate store inventorying. Each object contains the following fields: + | Field | Description | + |-------|-------------| + | StoreMachineName | A string indicating the machine name of the certificate location. | + | StorePath | A string indicating the path of the certificate location. | + | StoreType | An integer indicating the type of the certificate location. | + | Alias | A string indicating the alias of the certificate location. | + | ChainLevel | An integer indicating the chain level of the certificate location. | + | CertStoreId | A string indicating the certificate store ID of the certificate location. | + `, + }, + "metadata": schema.MapAttribute{ + ElementType: types.StringType, + Computed: true, + }, + "certificate_key_id": schema.Int64Attribute{ + Computed: true, + Description: "An integer indicating the Keyfactor Command reference ID of the certificate key.", + }, + "ca_row_index": schema.Int64Attribute{ + Computed: true, + Description: "An integer containing the CA's reference ID for certificate. Note: The CARowIndex has been replaced by CARecordId, but will remain for backward compatibility. It will only contain a non-zero value for certificates issued by Microsoft CAs. For Microsoft CA certificates, the CARowIndex will be equal to the CARecordId value parsed to an integer.", + MarkdownDescription: ` + An integer containing the CA's reference ID for certificate. + | :exclamation: Note: The CARowIndex has been replaced by CARecordId, but will remain for backward compatibility. It will only contain a non-zero value for certificates issued by Microsoft CAs. For Microsoft CA certificates, the CARowIndex will be equal to the CARecordId value parsed to an integer. | + |----------------------------------------------------------------------------------------------------------------| + `, + }, + "ca_record_id": schema.StringAttribute{ + Computed: true, + Description: "A string containing the CA's reference ID for certificate.", + }, + "detailed_key_usage": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "crl_sign": schema.BoolAttribute{ + Computed: true, + }, + "data_encipherment": schema.BoolAttribute{ + Computed: true, + }, + "decipher_only": schema.BoolAttribute{ + Computed: true, + }, + "digital_signature": schema.BoolAttribute{ + Computed: true, + }, + "encipher_only": schema.BoolAttribute{ + Computed: true, + }, + "hex_code": schema.StringAttribute{ + Computed: true, + }, + "key_agreement": schema.BoolAttribute{ + Computed: true, + }, + "key_cert_sign": schema.BoolAttribute{ + Computed: true, + }, + "key_encipherment": schema.BoolAttribute{ + Computed: true, + }, + "non_repudiation": schema.BoolAttribute{ + Computed: true, + }, + }, + Description: "An array of objects containing the detailed key usage of the certificate.", + MarkdownDescription: ` + An array of objects containing the detailed key usage of the certificate. Each object contains the following fields: + | Field | Description | + |-------|-------------| + | CrlSign | A boolean indicating whether the key can be used to sign a certificate revocation list (CRL). | + | DataEncipherment | A boolean indicating whether the key can be used for data encryption. | + | DecipherOnly | A boolean indicating whether the key can be used for decryption only. | + | DigitalSignature | A boolean indicating whether the key can be used for digital signatures. | + | EncipherOnly | A boolean indicating whether the key can be used for encryption only. | + | KeyAgreement | A boolean indicating whether the key can be used for key agreement. | + | KeyCertSign | A boolean indicating whether the key can be used to sign certificates. | + | KeyEncipherment | A boolean indicating whether the key can be used for key encryption. | + | NonRepudiation | A boolean indicating whether the key can be used for authentication. | + | HexCode | A string containing the hexadecimal code representing the total key usage. For example, a value of a0 would indicate digital signature with key encipherment. | + `, + Computed: true, + }, + "key_recoverable": schema.BoolAttribute{ + Computed: true, + Description: "A boolean indicating whether the certificate key is recoverable.", + }, + "curve": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the curve of the certificate.", + MarkdownDescription: ` + A string indicating the OID of the elliptic curve algorithm configured for the certificate, for ECC templates. Well-known OIDs include: + - 1.2.840.10045.3.1.7 = P-256/prime256v1/secp256r1 + - 1.3.132.0.34 = P-384/secp384r1 + - 1.3.132.0.35 = P-521/secp521r1 + `, + }, + "certificate_pem": schema.StringAttribute{ + Computed: true, + Description: "A string containing the certificate in PEM format.", + }, + "certificate_chain": schema.StringAttribute{ + Computed: true, + Description: "A string containing the certificate chain in PEM format.", + }, + "private_key": schema.StringAttribute{ + Computed: true, + Description: "A string containing the private key in PEM format.", + Sensitive: true, + }, + "key_password": schema.StringAttribute{ + Optional: true, + Description: "A string containing the password to encrypt the private key with.", + Sensitive: true, + }, + "ip_sans": schema.ListAttribute{ + Computed: true, + Description: "An array of strings containing the IP subject alternative names of the certificate.", + ElementType: types.StringType, + }, + "dns_sans": schema.ListAttribute{ + Computed: true, + Description: "An array of strings containing the DNS subject alternative names of the certificate.", + ElementType: types.StringType, + }, + "uri_sans": schema.ListAttribute{ + Computed: true, + Description: "An array of strings containing the URI subject alternative names of the certificate.", + ElementType: types.StringType, + }, + "include_private_key": schema.BoolAttribute{ + Computed: false, + Description: "A boolean indicating whether the private key should be retrieved from Keyfactor Command. Defaults to 'true'.", + Optional: true, + }, + "collection_id": schema.Int64Attribute{ + Computed: false, + Description: "An integer indicating the Keyfactor Command reference ID of the collection to search for the certificate. Defaults to '0'.", + Optional: true, + }, + }, + } + + //resp.Schema = CertificateDataSourceSchema(ctx) // this points to the generated provider schema +} + +func (d *CertificateDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*kfc.APIClient) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *kfc.APIClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + d.client = client +} + +func (d *CertificateDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data CertificateDataSourceModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + certIdentifier := data.Identifier.ValueString() + collectionId := data.CollectionId.ValueInt64() + ctx = tflog.SetField(ctx, "certificate_identifier", certIdentifier) + + // If the `identifier` attribute is an integer, use it to query the certificate + var ( + clientResp *kfc.ModelsCertificateRetrievalResponse + httpResp *http.Response + respErr error + ) + + if cID, ok := strconv.Atoi(certIdentifier); ok == nil { + clientResp, httpResp, respErr = readCertificateById(ctx, cID, d.client, collectionId) + } else if len(certIdentifier) == CertificateThumbprintLength { + clientResp, httpResp, respErr = lookupCertificate(ctx, CertificateThumbprintFieldName, certIdentifier, d.client, collectionId) + } else { + clientResp, httpResp, respErr = lookupCertificate(ctx, CertificateCNFieldName, certIdentifier, d.client, collectionId) + } + if clientResp == nil { + resp.Diagnostics.AddError("Invalid Certificate Identifier", fmt.Sprintf("Unable to find certificate %s in Keyfactor Command", certIdentifier)) + return + } else if respErr != nil { + ctx = tflog.SetField(ctx, "api_response", httpResp) + ctx = tflog.SetField(ctx, "api_error", respErr) + tflog.Error(ctx, "Unable to lookup certificate on Keyfactor Command") + resp.Diagnostics.AddError("Certificate Lookup Error", respErr.Error()) + return + } + + certPEM, ipSANs, dnsSANs, uriSANs, sanErr := ParseCertificateBytes(clientResp.ContentBytes) + if sanErr != nil { + tflog.Error(ctx, fmt.Sprintf("Unable to parse certificate SANs: %v", sanErr)) + resp.Diagnostics.AddError("SAN Parse Error", fmt.Sprintf("Unable to parse certificate SANs: %v", sanErr)) + } + tflog.Debug(ctx, fmt.Sprintf("IP SANs: %v", ipSANs)) + tflog.Debug(ctx, fmt.Sprintf("DNS SANs: %v", dnsSANs)) + tflog.Debug(ctx, fmt.Sprintf("URI SANs: %v", uriSANs)) + + if clientResp.KeyRecoverable != nil && *clientResp.KeyRecoverable { + tflog.Info(ctx, fmt.Sprintf("private key is recoverable for certificate '%s', attempting recovery", certIdentifier)) + tflog.Debug(ctx, fmt.Sprintf("KeyRecoverable: %v", *clientResp.KeyRecoverable)) + cId := int64(*clientResp.Id) + tflog.Debug(ctx, fmt.Sprintf("Calling RecoverPrivateKey for certificate '%d'", cId)) + pKey, leaf, chain, kErr := d.RecoverPrivateKey(ctx, cId, "", "", "", data.KeyPassword.String(), data.CollectionId.ValueInt64Pointer()) + if kErr != nil { + tflog.Error(ctx, fmt.Sprintf("Unable to recover private key: %v", kErr)) + resp.Diagnostics.AddError("Private Key Recovery Error", fmt.Sprintf("Unable to recover private key: %v", kErr)) + } + //tflog.Debug(ctx, fmt.Sprintf("Recovered private key: %v", pKey)) + tflog.SetField(ctx, "certificate_pem", leaf) + tflog.SetField(ctx, "certificate_chain", chain) + //tflog.SetField(ctx, "private_key", pKey) + tflog.Debug(ctx, "Recovered certificate private key from Keyfactor Command.") + data.PrivateKey = types.StringValue(pKey.(string)) + + var chainStr string + for _, cert := range chain { + chainLink := string(pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: cert.Raw, + })) + chainStr = chainStr + chainLink + tflog.Debug(ctx, chainLink) + } + + data.PEMChain = types.StringValue(chainStr) + } + + // Set the data model values from the response + data.Id = types.Int64Value(convertInt64Ptr(clientResp.Id)) + data.Thumbprint = types.StringValue(convertStringPtr(clientResp.Thumbprint)) + data.SerialNumber = types.StringValue(convertStringPtr(clientResp.SerialNumber)) + data.IssuedDN = types.StringValue(convertNullableStringPtr(&clientResp.IssuedDN)) + data.IssuedCN = types.StringValue(convertNullableStringPtr(&clientResp.IssuedCN)) + data.ImportDate = types.StringValue(convertTimeToStringPtr(clientResp.ImportDate)) + data.NotBefore = types.StringValue(convertTimeToStringPtr(clientResp.NotBefore)) + data.NotAfter = types.StringValue(convertTimeToStringPtr(clientResp.NotAfter)) + data.IssuerDN = types.StringValue(convertNullableStringPtr(&clientResp.IssuerDN)) + data.PrincipalId = types.Int64Value(convertNullableIntPtr(&clientResp.PrincipalId)) + data.TemplateId = types.Int64Value(convertNullableIntPtr(&clientResp.TemplateId)) + data.CertState = types.StringValue(convertStringPtr(clientResp.CertStateString)) + data.KeySizeInBits = types.Int64Value(convertInt64Ptr(clientResp.KeySizeInBits)) + data.KeyType = types.StringValue(convertStringPtr(clientResp.KeyTypeString)) + data.RequesterId = types.Int64Value(int64(*clientResp.RequesterId)) + data.IssuedOU = types.StringValue(convertNullableStringPtr(&clientResp.IssuedOU)) + data.IssuedEmail = types.StringValue(convertNullableStringPtr(&clientResp.IssuedEmail)) + data.KeyUsage = types.Int64Value(convertInt64Ptr(clientResp.KeyUsage)) + data.SigningAlgorithm = types.StringValue(convertStringPtr(clientResp.SigningAlgorithm)) + data.RevocationEffDate = types.StringValue(convertNullableTimePtr(&clientResp.RevocationEffDate)) + data.RevocationReason = types.Int64Value(convertNullableIntPtr(&clientResp.RevocationReason)) + data.RevocationComment = types.StringValue(convertNullableStringPtr(&clientResp.RevocationComment)) + data.CertificateAuthorityId = types.Int64Value(convertInt64Ptr(clientResp.CertificateAuthorityId)) + data.CertificateAuthorityName = types.StringValue(convertStringPtr(clientResp.CertificateAuthorityName)) + data.TemplateName = types.StringValue(convertStringPtr(clientResp.TemplateName)) + data.ArchivedKey = types.BoolValue(*clientResp.ArchivedKey) + data.HasPrivateKey = types.BoolValue(*clientResp.HasPrivateKey) + data.PrincipalName = types.StringValue(convertNullableStringPtr(&clientResp.PrincipalName)) + data.CertRequestId = types.Int64Value(convertInt64Ptr(clientResp.CertRequestId)) + data.RequesterName = types.StringValue(*clientResp.RequesterName) + data.ContentBytes = types.StringValue(convertStringPtr(clientResp.ContentBytes)) + + data.CertificateKeyId = types.Int64Value(convertInt64Ptr(clientResp.CertificateKeyId)) + data.CARowIndex = types.Int64Value(*clientResp.CARowIndex) + data.CARecordId = types.StringValue(convertStringPtr(clientResp.CARecordId)) + data.DetailedKeyUsage = d.setDetailedKeyUsage(clientResp.DetailedKeyUsage) + data.KeyRecoverable = types.BoolValue(*clientResp.KeyRecoverable) + data.Curve = types.StringValue(convertNullableStringPtr(&clientResp.Curve)) + + data.ExtendedKeyUsages = d.setExtendedKeyUsage(clientResp.ExtendedKeyUsages) + data.CRLDistributionPoints = d.setCRLEndpoints(clientResp.CRLDistributionPoints) + data.LocationsCount = d.setLocationsCounts(clientResp.LocationsCount) + + data.IPSANs, _ = convertToTerraformList(ipSANs) + data.DNSSANs, _ = convertToTerraformList(dnsSANs) + data.URISANs, _ = convertToTerraformList(uriSANs) + + data.Metadata, _ = convertToTerraformMap(clientResp.Metadata) + + // Terraform Specific Values + data.PEM = types.StringValue(certPEM) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (d *CertificateDataSource) convertJSONToTerraformModel(jsonString string) (*CertificateDataSourceModel, error) { + var data CertificateDataSourceModel + + // read json string into data model + jErr := json.Unmarshal([]byte(jsonString), &data) + + if jErr != nil { + return nil, jErr + } + + return &data, nil +} + +//func (d *CertificateDataSource) setLocations(locations []kfc.ModelsCertificateRetrievalResponseCertificateStoreLocationDetailModel) []CertificateLocation { +// var formattedLocations []CertificateLocation +// +// if len(locations) > 0 { +// for _, location := range locations { +// certLocation := CertificateLocation{ +// StoreMachineName: types.StringValue(*location.), +// StorePath: types.StringValue(*location.StorePath), +// StoreType: types.StringValue(*location.StoreType), +// Alias: types.StringValue(*location.Alias), +// ChainLevel: types.Int64Value(int64(*location.ChainLevel)), +// CertStoreId: types.StringValue(*location.CertStoreId), +// } +// formattedLocations = append(formattedLocations, certLocation) +// } +// } +// return formattedLocations +//} + +func (d *CertificateDataSource) setLocationsCounts(locations []kfc.ModelsCertificateRetrievalResponseLocationCountModel) []CertificateLocationCount { + var formattedLocations []CertificateLocationCount + + if len(locations) > 0 { + for _, location := range locations { + certLocation := CertificateLocationCount{ + StoreType: types.StringValue(*location.Type), + Count: types.Int64Value(int64(*location.Count)), + } + formattedLocations = append(formattedLocations, certLocation) + } + } + return formattedLocations +} + +func (d *CertificateDataSource) setDetailedKeyUsage(usage *kfc.ModelsCertificateRetrievalResponseDetailedKeyUsageModel) *CertificateDetailedKeyUsage { + var formattedUsage CertificateDetailedKeyUsage + + if usage != nil { + formattedUsage = CertificateDetailedKeyUsage{ + CrlSign: types.BoolValue(*usage.CrlSign), + DataEncipherment: types.BoolValue(*usage.DataEncipherment), + DecipherOnly: types.BoolValue(*usage.DecipherOnly), + DigitalSignature: types.BoolValue(*usage.DigitalSignature), + EncipherOnly: types.BoolValue(*usage.EncipherOnly), + KeyAgreement: types.BoolValue(*usage.KeyAgreement), + KeyCertSign: types.BoolValue(*usage.KeyCertSign), + KeyEncipherment: types.BoolValue(*usage.KeyEncipherment), + NonRepudiation: types.BoolValue(*usage.NonRepudiation), + HexCode: types.StringValue(*usage.HexCode), + } + } + return &formattedUsage +} + +func (d *CertificateDataSource) setExtendedKeyUsage(usages []kfc.ModelsCertificateRetrievalResponseExtendedKeyUsageModel) []CertificateExtendedKeyUsage { + var formattedUsages []CertificateExtendedKeyUsage + + if len(usages) > 0 { + for _, usage := range usages { + extKeyUsage := CertificateExtendedKeyUsage{ + Id: types.Int64Value(int64(*usage.Id)), + Oid: types.StringValue(*usage.Oid), + DisplayName: types.StringValue(*usage.DisplayName), + } + formattedUsages = append(formattedUsages, extKeyUsage) + } + } + return formattedUsages +} + +func (d *CertificateDataSource) setCRLEndpoints(crls []kfc.ModelsCertificateRetrievalResponseCRLDistributionPointModel) []CertificateCRLDistributionPoint { + var formattedCRLs []CertificateCRLDistributionPoint + + if len(crls) > 0 { + for _, crl := range crls { + crlDistPoint := CertificateCRLDistributionPoint{ + Id: types.Int64Value(int64(*crl.Id)), + Url: types.StringValue(*crl.Url), + UrlHash: types.StringValue(*crl.UrlHash), + } + formattedCRLs = append(formattedCRLs, crlDistPoint) + } + } + return formattedCRLs +} + +func (d *CertificateDataSource) RecoverPrivateKey(ctx context.Context, id int64, thumbPrint string, sn string, dn string, password string, collectionID *int64) (interface{}, *x509.Certificate, []*x509.Certificate, error) { + var colIDPtr *int32 + if collectionID != nil { + colID := int32(*collectionID) + colIDPtr = &colID + } + + return recoverPrivateKey(ctx, d.client, id, thumbPrint, sn, dn, password, colIDPtr) +} diff --git a/internal/provider/v3/command/data_source_keyfactor_certificate_store.go b/internal/provider/v3/command/data_source_keyfactor_certificate_store.go new file mode 100644 index 00000000..8298ec85 --- /dev/null +++ b/internal/provider/v3/command/data_source_keyfactor_certificate_store.go @@ -0,0 +1,4071 @@ +package command + +import ( + "context" + "fmt" + kfc "github.com/Keyfactor/keyfactor-go-client-sdk/v2/api/command" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" + "github.com/hashicorp/terraform-plugin-log/tflog" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" +) + +type KeyfactorCertificateStoreModel struct { + AgentAssigned types.Bool `tfsdk:"agent_assigned"` + AgentId types.String `tfsdk:"agent_id"` + Approved types.Bool `tfsdk:"approved"` + CertStoreInventoryJobId types.String `tfsdk:"cert_store_inventory_job_id"` + CertStoreType types.Int64 `tfsdk:"cert_store_type"` + ClientMachine types.String `tfsdk:"client_machine"` + ContainerId types.Int64 `tfsdk:"container_id"` + ContainerName types.String `tfsdk:"container_name"` + CreateIfMissing types.Bool `tfsdk:"create_if_missing"` + DisplayName types.String `tfsdk:"display_name"` + Id types.String `tfsdk:"id"` + InventorySchedule InventoryScheduleValue `tfsdk:"inventory_schedule"` + Properties types.String `tfsdk:"properties"` + ReenrollmentStatus ReenrollmentStatusValue `tfsdk:"reenrollment_status"` + SetNewPasswordAllowed types.Bool `tfsdk:"set_new_password_allowed"` + Storepath types.String `tfsdk:"storepath"` +} + +var _ basetypes.ObjectTypable = InventoryScheduleType{} + +type InventoryScheduleType struct { + basetypes.ObjectType +} + +func (t InventoryScheduleType) Equal(o attr.Type) bool { + other, ok := o.(InventoryScheduleType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t InventoryScheduleType) String() string { + return "InventoryScheduleType" +} + +func (t InventoryScheduleType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + dailyAttribute, ok := attributes["daily"] + + if !ok { + diags.AddError( + "Attribute Missing", + `daily is missing from object`) + + return nil, diags + } + + dailyVal, ok := dailyAttribute.(basetypes.ObjectValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`daily expected to be basetypes.ObjectValue, was: %T`, dailyAttribute)) + } + + exactlyOnceAttribute, ok := attributes["exactly_once"] + + if !ok { + diags.AddError( + "Attribute Missing", + `exactly_once is missing from object`) + + return nil, diags + } + + exactlyOnceVal, ok := exactlyOnceAttribute.(basetypes.ObjectValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`exactly_once expected to be basetypes.ObjectValue, was: %T`, exactlyOnceAttribute)) + } + + immediateAttribute, ok := attributes["immediate"] + + if !ok { + diags.AddError( + "Attribute Missing", + `immediate is missing from object`) + + return nil, diags + } + + immediateVal, ok := immediateAttribute.(basetypes.BoolValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`immediate expected to be basetypes.BoolValue, was: %T`, immediateAttribute)) + } + + intervalAttribute, ok := attributes["interval"] + + if !ok { + diags.AddError( + "Attribute Missing", + `interval is missing from object`) + + return nil, diags + } + + intervalVal, ok := intervalAttribute.(basetypes.ObjectValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`interval expected to be basetypes.ObjectValue, was: %T`, intervalAttribute)) + } + + monthlyAttribute, ok := attributes["monthly"] + + if !ok { + diags.AddError( + "Attribute Missing", + `monthly is missing from object`) + + return nil, diags + } + + monthlyVal, ok := monthlyAttribute.(basetypes.ObjectValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`monthly expected to be basetypes.ObjectValue, was: %T`, monthlyAttribute)) + } + + weeklyAttribute, ok := attributes["weekly"] + + if !ok { + diags.AddError( + "Attribute Missing", + `weekly is missing from object`) + + return nil, diags + } + + weeklyVal, ok := weeklyAttribute.(basetypes.ObjectValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`weekly expected to be basetypes.ObjectValue, was: %T`, weeklyAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return InventoryScheduleValue{ + Daily: dailyVal, + ExactlyOnce: exactlyOnceVal, + Immediate: immediateVal, + Interval: intervalVal, + Monthly: monthlyVal, + Weekly: weeklyVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewInventoryScheduleValueNull() InventoryScheduleValue { + return InventoryScheduleValue{ + state: attr.ValueStateNull, + } +} + +func NewInventoryScheduleValueUnknown() InventoryScheduleValue { + return InventoryScheduleValue{ + state: attr.ValueStateUnknown, + } +} + +func NewInventoryScheduleValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (InventoryScheduleValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing InventoryScheduleValue Attribute Value", + "While creating a InventoryScheduleValue value, a missing attribute value was detected. "+ + "A InventoryScheduleValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("InventoryScheduleValue Attribute Name (%s) Expected ParamType: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid InventoryScheduleValue Attribute ParamType", + "While creating a InventoryScheduleValue value, an invalid attribute value was detected. "+ + "A InventoryScheduleValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("InventoryScheduleValue Attribute Name (%s) Expected ParamType: %s\n", name, attributeType.String())+ + fmt.Sprintf("InventoryScheduleValue Attribute Name (%s) Given ParamType: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra InventoryScheduleValue Attribute Value", + "While creating a InventoryScheduleValue value, an extra attribute value was detected. "+ + "A InventoryScheduleValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra InventoryScheduleValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewInventoryScheduleValueUnknown(), diags + } + + dailyAttribute, ok := attributes["daily"] + + if !ok { + diags.AddError( + "Attribute Missing", + `daily is missing from object`) + + return NewInventoryScheduleValueUnknown(), diags + } + + dailyVal, ok := dailyAttribute.(basetypes.ObjectValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`daily expected to be basetypes.ObjectValue, was: %T`, dailyAttribute)) + } + + exactlyOnceAttribute, ok := attributes["exactly_once"] + + if !ok { + diags.AddError( + "Attribute Missing", + `exactly_once is missing from object`) + + return NewInventoryScheduleValueUnknown(), diags + } + + exactlyOnceVal, ok := exactlyOnceAttribute.(basetypes.ObjectValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`exactly_once expected to be basetypes.ObjectValue, was: %T`, exactlyOnceAttribute)) + } + + immediateAttribute, ok := attributes["immediate"] + + if !ok { + diags.AddError( + "Attribute Missing", + `immediate is missing from object`) + + return NewInventoryScheduleValueUnknown(), diags + } + + immediateVal, ok := immediateAttribute.(basetypes.BoolValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`immediate expected to be basetypes.BoolValue, was: %T`, immediateAttribute)) + } + + intervalAttribute, ok := attributes["interval"] + + if !ok { + diags.AddError( + "Attribute Missing", + `interval is missing from object`) + + return NewInventoryScheduleValueUnknown(), diags + } + + intervalVal, ok := intervalAttribute.(basetypes.ObjectValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`interval expected to be basetypes.ObjectValue, was: %T`, intervalAttribute)) + } + + monthlyAttribute, ok := attributes["monthly"] + + if !ok { + diags.AddError( + "Attribute Missing", + `monthly is missing from object`) + + return NewInventoryScheduleValueUnknown(), diags + } + + monthlyVal, ok := monthlyAttribute.(basetypes.ObjectValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`monthly expected to be basetypes.ObjectValue, was: %T`, monthlyAttribute)) + } + + weeklyAttribute, ok := attributes["weekly"] + + if !ok { + diags.AddError( + "Attribute Missing", + `weekly is missing from object`) + + return NewInventoryScheduleValueUnknown(), diags + } + + weeklyVal, ok := weeklyAttribute.(basetypes.ObjectValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`weekly expected to be basetypes.ObjectValue, was: %T`, weeklyAttribute)) + } + + if diags.HasError() { + return NewInventoryScheduleValueUnknown(), diags + } + + return InventoryScheduleValue{ + Daily: dailyVal, + ExactlyOnce: exactlyOnceVal, + Immediate: immediateVal, + Interval: intervalVal, + Monthly: monthlyVal, + Weekly: weeklyVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewInventoryScheduleValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) InventoryScheduleValue { + object, diags := NewInventoryScheduleValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewInventoryScheduleValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t InventoryScheduleType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewInventoryScheduleValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewInventoryScheduleValueUnknown(), nil + } + + if in.IsNull() { + return NewInventoryScheduleValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewInventoryScheduleValueMust(t.AttrTypes, attributes), nil +} + +func (t InventoryScheduleType) ValueType(ctx context.Context) attr.Value { + return InventoryScheduleValue{} +} + +var _ basetypes.ObjectValuable = InventoryScheduleValue{} + +type InventoryScheduleValue struct { + Daily basetypes.ObjectValue `tfsdk:"daily"` + ExactlyOnce basetypes.ObjectValue `tfsdk:"exactly_once"` + Immediate basetypes.BoolValue `tfsdk:"immediate"` + Interval basetypes.ObjectValue `tfsdk:"interval"` + Monthly basetypes.ObjectValue `tfsdk:"monthly"` + Weekly basetypes.ObjectValue `tfsdk:"weekly"` + state attr.ValueState +} + +func (v InventoryScheduleValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 6) + + var val tftypes.Value + var err error + + attrTypes["daily"] = basetypes.ObjectType{ + AttrTypes: DailyValue{}.AttributeTypes(ctx), + }.TerraformType(ctx) + attrTypes["exactly_once"] = basetypes.ObjectType{ + AttrTypes: ExactlyOnceValue{}.AttributeTypes(ctx), + }.TerraformType(ctx) + attrTypes["immediate"] = basetypes.BoolType{}.TerraformType(ctx) + attrTypes["interval"] = basetypes.ObjectType{ + AttrTypes: IntervalValue{}.AttributeTypes(ctx), + }.TerraformType(ctx) + attrTypes["monthly"] = basetypes.ObjectType{ + AttrTypes: MonthlyValue{}.AttributeTypes(ctx), + }.TerraformType(ctx) + attrTypes["weekly"] = basetypes.ObjectType{ + AttrTypes: WeeklyValue{}.AttributeTypes(ctx), + }.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 6) + + val, err = v.Daily.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["daily"] = val + + val, err = v.ExactlyOnce.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["exactly_once"] = val + + val, err = v.Immediate.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["immediate"] = val + + val, err = v.Interval.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["interval"] = val + + val, err = v.Monthly.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["monthly"] = val + + val, err = v.Weekly.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["weekly"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v InventoryScheduleValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v InventoryScheduleValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v InventoryScheduleValue) String() string { + return "InventoryScheduleValue" +} + +func (v InventoryScheduleValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + var daily basetypes.ObjectValue + + if v.Daily.IsNull() { + daily = types.ObjectNull( + DailyValue{}.AttributeTypes(ctx), + ) + } + + if v.Daily.IsUnknown() { + daily = types.ObjectUnknown( + DailyValue{}.AttributeTypes(ctx), + ) + } + + if !v.Daily.IsNull() && !v.Daily.IsUnknown() { + daily = types.ObjectValueMust( + DailyValue{}.AttributeTypes(ctx), + v.Daily.Attributes(), + ) + } + + var exactlyOnce basetypes.ObjectValue + + if v.ExactlyOnce.IsNull() { + exactlyOnce = types.ObjectNull( + ExactlyOnceValue{}.AttributeTypes(ctx), + ) + } + + if v.ExactlyOnce.IsUnknown() { + exactlyOnce = types.ObjectUnknown( + ExactlyOnceValue{}.AttributeTypes(ctx), + ) + } + + if !v.ExactlyOnce.IsNull() && !v.ExactlyOnce.IsUnknown() { + exactlyOnce = types.ObjectValueMust( + ExactlyOnceValue{}.AttributeTypes(ctx), + v.ExactlyOnce.Attributes(), + ) + } + + var interval basetypes.ObjectValue + + if v.Interval.IsNull() { + interval = types.ObjectNull( + IntervalValue{}.AttributeTypes(ctx), + ) + } + + if v.Interval.IsUnknown() { + interval = types.ObjectUnknown( + IntervalValue{}.AttributeTypes(ctx), + ) + } + + if !v.Interval.IsNull() && !v.Interval.IsUnknown() { + interval = types.ObjectValueMust( + IntervalValue{}.AttributeTypes(ctx), + v.Interval.Attributes(), + ) + } + + var monthly basetypes.ObjectValue + + if v.Monthly.IsNull() { + monthly = types.ObjectNull( + MonthlyValue{}.AttributeTypes(ctx), + ) + } + + if v.Monthly.IsUnknown() { + monthly = types.ObjectUnknown( + MonthlyValue{}.AttributeTypes(ctx), + ) + } + + if !v.Monthly.IsNull() && !v.Monthly.IsUnknown() { + monthly = types.ObjectValueMust( + MonthlyValue{}.AttributeTypes(ctx), + v.Monthly.Attributes(), + ) + } + + var weekly basetypes.ObjectValue + + if v.Weekly.IsNull() { + weekly = types.ObjectNull( + WeeklyValue{}.AttributeTypes(ctx), + ) + } + + if v.Weekly.IsUnknown() { + weekly = types.ObjectUnknown( + WeeklyValue{}.AttributeTypes(ctx), + ) + } + + if !v.Weekly.IsNull() && !v.Weekly.IsUnknown() { + weekly = types.ObjectValueMust( + WeeklyValue{}.AttributeTypes(ctx), + v.Weekly.Attributes(), + ) + } + + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "daily": basetypes.ObjectType{ + AttrTypes: DailyValue{}.AttributeTypes(ctx), + }, + "exactly_once": basetypes.ObjectType{ + AttrTypes: ExactlyOnceValue{}.AttributeTypes(ctx), + }, + "immediate": basetypes.BoolType{}, + "interval": basetypes.ObjectType{ + AttrTypes: IntervalValue{}.AttributeTypes(ctx), + }, + "monthly": basetypes.ObjectType{ + AttrTypes: MonthlyValue{}.AttributeTypes(ctx), + }, + "weekly": basetypes.ObjectType{ + AttrTypes: WeeklyValue{}.AttributeTypes(ctx), + }, + }, + map[string]attr.Value{ + "daily": daily, + "exactly_once": exactlyOnce, + "immediate": v.Immediate, + "interval": interval, + "monthly": monthly, + "weekly": weekly, + }) + + return objVal, diags +} + +func (v InventoryScheduleValue) Equal(o attr.Value) bool { + other, ok := o.(InventoryScheduleValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Daily.Equal(other.Daily) { + return false + } + + if !v.ExactlyOnce.Equal(other.ExactlyOnce) { + return false + } + + if !v.Immediate.Equal(other.Immediate) { + return false + } + + if !v.Interval.Equal(other.Interval) { + return false + } + + if !v.Monthly.Equal(other.Monthly) { + return false + } + + if !v.Weekly.Equal(other.Weekly) { + return false + } + + return true +} + +func (v InventoryScheduleValue) Type(ctx context.Context) attr.Type { + return InventoryScheduleType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v InventoryScheduleValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "daily": basetypes.ObjectType{ + AttrTypes: DailyValue{}.AttributeTypes(ctx), + }, + "exactly_once": basetypes.ObjectType{ + AttrTypes: ExactlyOnceValue{}.AttributeTypes(ctx), + }, + "immediate": basetypes.BoolType{}, + "interval": basetypes.ObjectType{ + AttrTypes: IntervalValue{}.AttributeTypes(ctx), + }, + "monthly": basetypes.ObjectType{ + AttrTypes: MonthlyValue{}.AttributeTypes(ctx), + }, + "weekly": basetypes.ObjectType{ + AttrTypes: WeeklyValue{}.AttributeTypes(ctx), + }, + } +} + +var _ basetypes.ObjectTypable = DailyType{} + +type DailyType struct { + basetypes.ObjectType +} + +func (t DailyType) Equal(o attr.Type) bool { + other, ok := o.(DailyType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t DailyType) String() string { + return "DailyType" +} + +func (t DailyType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + timeAttribute, ok := attributes["time"] + + if !ok { + diags.AddError( + "Attribute Missing", + `time is missing from object`) + + return nil, diags + } + + timeVal, ok := timeAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`time expected to be basetypes.StringValue, was: %T`, timeAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return DailyValue{ + Time: timeVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewDailyValueNull() DailyValue { + return DailyValue{ + state: attr.ValueStateNull, + } +} + +func NewDailyValueUnknown() DailyValue { + return DailyValue{ + state: attr.ValueStateUnknown, + } +} + +func NewDailyValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (DailyValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing DailyValue Attribute Value", + "While creating a DailyValue value, a missing attribute value was detected. "+ + "A DailyValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("DailyValue Attribute Name (%s) Expected ParamType: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid DailyValue Attribute ParamType", + "While creating a DailyValue value, an invalid attribute value was detected. "+ + "A DailyValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("DailyValue Attribute Name (%s) Expected ParamType: %s\n", name, attributeType.String())+ + fmt.Sprintf("DailyValue Attribute Name (%s) Given ParamType: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra DailyValue Attribute Value", + "While creating a DailyValue value, an extra attribute value was detected. "+ + "A DailyValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra DailyValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewDailyValueUnknown(), diags + } + + timeAttribute, ok := attributes["time"] + + if !ok { + diags.AddError( + "Attribute Missing", + `time is missing from object`) + + return NewDailyValueUnknown(), diags + } + + timeVal, ok := timeAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`time expected to be basetypes.StringValue, was: %T`, timeAttribute)) + } + + if diags.HasError() { + return NewDailyValueUnknown(), diags + } + + return DailyValue{ + Time: timeVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewDailyValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) DailyValue { + object, diags := NewDailyValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewDailyValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t DailyType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewDailyValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewDailyValueUnknown(), nil + } + + if in.IsNull() { + return NewDailyValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewDailyValueMust(t.AttrTypes, attributes), nil +} + +func (t DailyType) ValueType(ctx context.Context) attr.Value { + return DailyValue{} +} + +var _ basetypes.ObjectValuable = DailyValue{} + +type DailyValue struct { + Time basetypes.StringValue `tfsdk:"time"` + state attr.ValueState +} + +func (v DailyValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 1) + + var val tftypes.Value + var err error + + attrTypes["time"] = basetypes.StringType{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 1) + + val, err = v.Time.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["time"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v DailyValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v DailyValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v DailyValue) String() string { + return "DailyValue" +} + +func (v DailyValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "time": basetypes.StringType{}, + }, + map[string]attr.Value{ + "time": v.Time, + }) + + return objVal, diags +} + +func (v DailyValue) Equal(o attr.Value) bool { + other, ok := o.(DailyValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Time.Equal(other.Time) { + return false + } + + return true +} + +func (v DailyValue) Type(ctx context.Context) attr.Type { + return DailyType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v DailyValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "time": basetypes.StringType{}, + } +} + +var _ basetypes.ObjectTypable = ExactlyOnceType{} + +type ExactlyOnceType struct { + basetypes.ObjectType +} + +func (t ExactlyOnceType) Equal(o attr.Type) bool { + other, ok := o.(ExactlyOnceType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t ExactlyOnceType) String() string { + return "ExactlyOnceType" +} + +func (t ExactlyOnceType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + timeAttribute, ok := attributes["time"] + + if !ok { + diags.AddError( + "Attribute Missing", + `time is missing from object`) + + return nil, diags + } + + timeVal, ok := timeAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`time expected to be basetypes.StringValue, was: %T`, timeAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return ExactlyOnceValue{ + Time: timeVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewExactlyOnceValueNull() ExactlyOnceValue { + return ExactlyOnceValue{ + state: attr.ValueStateNull, + } +} + +func NewExactlyOnceValueUnknown() ExactlyOnceValue { + return ExactlyOnceValue{ + state: attr.ValueStateUnknown, + } +} + +func NewExactlyOnceValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (ExactlyOnceValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing ExactlyOnceValue Attribute Value", + "While creating a ExactlyOnceValue value, a missing attribute value was detected. "+ + "A ExactlyOnceValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("ExactlyOnceValue Attribute Name (%s) Expected ParamType: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid ExactlyOnceValue Attribute ParamType", + "While creating a ExactlyOnceValue value, an invalid attribute value was detected. "+ + "A ExactlyOnceValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("ExactlyOnceValue Attribute Name (%s) Expected ParamType: %s\n", name, attributeType.String())+ + fmt.Sprintf("ExactlyOnceValue Attribute Name (%s) Given ParamType: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra ExactlyOnceValue Attribute Value", + "While creating a ExactlyOnceValue value, an extra attribute value was detected. "+ + "A ExactlyOnceValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra ExactlyOnceValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewExactlyOnceValueUnknown(), diags + } + + timeAttribute, ok := attributes["time"] + + if !ok { + diags.AddError( + "Attribute Missing", + `time is missing from object`) + + return NewExactlyOnceValueUnknown(), diags + } + + timeVal, ok := timeAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`time expected to be basetypes.StringValue, was: %T`, timeAttribute)) + } + + if diags.HasError() { + return NewExactlyOnceValueUnknown(), diags + } + + return ExactlyOnceValue{ + Time: timeVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewExactlyOnceValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) ExactlyOnceValue { + object, diags := NewExactlyOnceValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewExactlyOnceValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t ExactlyOnceType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewExactlyOnceValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewExactlyOnceValueUnknown(), nil + } + + if in.IsNull() { + return NewExactlyOnceValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewExactlyOnceValueMust(t.AttrTypes, attributes), nil +} + +func (t ExactlyOnceType) ValueType(ctx context.Context) attr.Value { + return ExactlyOnceValue{} +} + +var _ basetypes.ObjectValuable = ExactlyOnceValue{} + +type ExactlyOnceValue struct { + Time basetypes.StringValue `tfsdk:"time"` + state attr.ValueState +} + +func (v ExactlyOnceValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 1) + + var val tftypes.Value + var err error + + attrTypes["time"] = basetypes.StringType{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 1) + + val, err = v.Time.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["time"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v ExactlyOnceValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v ExactlyOnceValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v ExactlyOnceValue) String() string { + return "ExactlyOnceValue" +} + +func (v ExactlyOnceValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "time": basetypes.StringType{}, + }, + map[string]attr.Value{ + "time": v.Time, + }) + + return objVal, diags +} + +func (v ExactlyOnceValue) Equal(o attr.Value) bool { + other, ok := o.(ExactlyOnceValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Time.Equal(other.Time) { + return false + } + + return true +} + +func (v ExactlyOnceValue) Type(ctx context.Context) attr.Type { + return ExactlyOnceType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v ExactlyOnceValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "time": basetypes.StringType{}, + } +} + +var _ basetypes.ObjectTypable = IntervalType{} + +type IntervalType struct { + basetypes.ObjectType +} + +func (t IntervalType) Equal(o attr.Type) bool { + other, ok := o.(IntervalType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t IntervalType) String() string { + return "IntervalType" +} + +func (t IntervalType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + minutesAttribute, ok := attributes["minutes"] + + if !ok { + diags.AddError( + "Attribute Missing", + `minutes is missing from object`) + + return nil, diags + } + + minutesVal, ok := minutesAttribute.(basetypes.Int64Value) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`minutes expected to be basetypes.Int64Value, was: %T`, minutesAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return IntervalValue{ + Minutes: minutesVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewIntervalValueNull() IntervalValue { + return IntervalValue{ + state: attr.ValueStateNull, + } +} + +func NewIntervalValueUnknown() IntervalValue { + return IntervalValue{ + state: attr.ValueStateUnknown, + } +} + +func NewIntervalValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (IntervalValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing IntervalValue Attribute Value", + "While creating a IntervalValue value, a missing attribute value was detected. "+ + "A IntervalValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("IntervalValue Attribute Name (%s) Expected ParamType: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid IntervalValue Attribute ParamType", + "While creating a IntervalValue value, an invalid attribute value was detected. "+ + "A IntervalValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("IntervalValue Attribute Name (%s) Expected ParamType: %s\n", name, attributeType.String())+ + fmt.Sprintf("IntervalValue Attribute Name (%s) Given ParamType: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra IntervalValue Attribute Value", + "While creating a IntervalValue value, an extra attribute value was detected. "+ + "A IntervalValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra IntervalValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewIntervalValueUnknown(), diags + } + + minutesAttribute, ok := attributes["minutes"] + + if !ok { + diags.AddError( + "Attribute Missing", + `minutes is missing from object`) + + return NewIntervalValueUnknown(), diags + } + + minutesVal, ok := minutesAttribute.(basetypes.Int64Value) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`minutes expected to be basetypes.Int64Value, was: %T`, minutesAttribute)) + } + + if diags.HasError() { + return NewIntervalValueUnknown(), diags + } + + return IntervalValue{ + Minutes: minutesVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewIntervalValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) IntervalValue { + object, diags := NewIntervalValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewIntervalValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t IntervalType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewIntervalValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewIntervalValueUnknown(), nil + } + + if in.IsNull() { + return NewIntervalValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewIntervalValueMust(t.AttrTypes, attributes), nil +} + +func (t IntervalType) ValueType(ctx context.Context) attr.Value { + return IntervalValue{} +} + +var _ basetypes.ObjectValuable = IntervalValue{} + +type IntervalValue struct { + Minutes basetypes.Int64Value `tfsdk:"minutes"` + state attr.ValueState +} + +func (v IntervalValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 1) + + var val tftypes.Value + var err error + + attrTypes["minutes"] = basetypes.Int64Type{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 1) + + val, err = v.Minutes.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["minutes"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v IntervalValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v IntervalValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v IntervalValue) String() string { + return "IntervalValue" +} + +func (v IntervalValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "minutes": basetypes.Int64Type{}, + }, + map[string]attr.Value{ + "minutes": v.Minutes, + }) + + return objVal, diags +} + +func (v IntervalValue) Equal(o attr.Value) bool { + other, ok := o.(IntervalValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Minutes.Equal(other.Minutes) { + return false + } + + return true +} + +func (v IntervalValue) Type(ctx context.Context) attr.Type { + return IntervalType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v IntervalValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "minutes": basetypes.Int64Type{}, + } +} + +var _ basetypes.ObjectTypable = MonthlyType{} + +type MonthlyType struct { + basetypes.ObjectType +} + +func (t MonthlyType) Equal(o attr.Type) bool { + other, ok := o.(MonthlyType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t MonthlyType) String() string { + return "MonthlyType" +} + +func (t MonthlyType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + dayAttribute, ok := attributes["day"] + + if !ok { + diags.AddError( + "Attribute Missing", + `day is missing from object`) + + return nil, diags + } + + dayVal, ok := dayAttribute.(basetypes.Int64Value) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`day expected to be basetypes.Int64Value, was: %T`, dayAttribute)) + } + + timeAttribute, ok := attributes["time"] + + if !ok { + diags.AddError( + "Attribute Missing", + `time is missing from object`) + + return nil, diags + } + + timeVal, ok := timeAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`time expected to be basetypes.StringValue, was: %T`, timeAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return MonthlyValue{ + Day: dayVal, + Time: timeVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewMonthlyValueNull() MonthlyValue { + return MonthlyValue{ + state: attr.ValueStateNull, + } +} + +func NewMonthlyValueUnknown() MonthlyValue { + return MonthlyValue{ + state: attr.ValueStateUnknown, + } +} + +func NewMonthlyValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (MonthlyValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing MonthlyValue Attribute Value", + "While creating a MonthlyValue value, a missing attribute value was detected. "+ + "A MonthlyValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("MonthlyValue Attribute Name (%s) Expected ParamType: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid MonthlyValue Attribute ParamType", + "While creating a MonthlyValue value, an invalid attribute value was detected. "+ + "A MonthlyValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("MonthlyValue Attribute Name (%s) Expected ParamType: %s\n", name, attributeType.String())+ + fmt.Sprintf("MonthlyValue Attribute Name (%s) Given ParamType: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra MonthlyValue Attribute Value", + "While creating a MonthlyValue value, an extra attribute value was detected. "+ + "A MonthlyValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra MonthlyValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewMonthlyValueUnknown(), diags + } + + dayAttribute, ok := attributes["day"] + + if !ok { + diags.AddError( + "Attribute Missing", + `day is missing from object`) + + return NewMonthlyValueUnknown(), diags + } + + dayVal, ok := dayAttribute.(basetypes.Int64Value) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`day expected to be basetypes.Int64Value, was: %T`, dayAttribute)) + } + + timeAttribute, ok := attributes["time"] + + if !ok { + diags.AddError( + "Attribute Missing", + `time is missing from object`) + + return NewMonthlyValueUnknown(), diags + } + + timeVal, ok := timeAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`time expected to be basetypes.StringValue, was: %T`, timeAttribute)) + } + + if diags.HasError() { + return NewMonthlyValueUnknown(), diags + } + + return MonthlyValue{ + Day: dayVal, + Time: timeVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewMonthlyValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) MonthlyValue { + object, diags := NewMonthlyValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewMonthlyValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t MonthlyType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewMonthlyValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewMonthlyValueUnknown(), nil + } + + if in.IsNull() { + return NewMonthlyValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewMonthlyValueMust(t.AttrTypes, attributes), nil +} + +func (t MonthlyType) ValueType(ctx context.Context) attr.Value { + return MonthlyValue{} +} + +var _ basetypes.ObjectValuable = MonthlyValue{} + +type MonthlyValue struct { + Day basetypes.Int64Value `tfsdk:"day"` + Time basetypes.StringValue `tfsdk:"time"` + state attr.ValueState +} + +func (v MonthlyValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 2) + + var val tftypes.Value + var err error + + attrTypes["day"] = basetypes.Int64Type{}.TerraformType(ctx) + attrTypes["time"] = basetypes.StringType{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 2) + + val, err = v.Day.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["day"] = val + + val, err = v.Time.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["time"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v MonthlyValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v MonthlyValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v MonthlyValue) String() string { + return "MonthlyValue" +} + +func (v MonthlyValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "day": basetypes.Int64Type{}, + "time": basetypes.StringType{}, + }, + map[string]attr.Value{ + "day": v.Day, + "time": v.Time, + }) + + return objVal, diags +} + +func (v MonthlyValue) Equal(o attr.Value) bool { + other, ok := o.(MonthlyValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Day.Equal(other.Day) { + return false + } + + if !v.Time.Equal(other.Time) { + return false + } + + return true +} + +func (v MonthlyValue) Type(ctx context.Context) attr.Type { + return MonthlyType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v MonthlyValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "day": basetypes.Int64Type{}, + "time": basetypes.StringType{}, + } +} + +var _ basetypes.ObjectTypable = WeeklyType{} + +type WeeklyType struct { + basetypes.ObjectType +} + +func (t WeeklyType) Equal(o attr.Type) bool { + other, ok := o.(WeeklyType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t WeeklyType) String() string { + return "WeeklyType" +} + +func (t WeeklyType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + daysAttribute, ok := attributes["days"] + + if !ok { + diags.AddError( + "Attribute Missing", + `days is missing from object`) + + return nil, diags + } + + daysVal, ok := daysAttribute.(basetypes.ListValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`days expected to be basetypes.ListValue, was: %T`, daysAttribute)) + } + + timeAttribute, ok := attributes["time"] + + if !ok { + diags.AddError( + "Attribute Missing", + `time is missing from object`) + + return nil, diags + } + + timeVal, ok := timeAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`time expected to be basetypes.StringValue, was: %T`, timeAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return WeeklyValue{ + Days: daysVal, + Time: timeVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewWeeklyValueNull() WeeklyValue { + return WeeklyValue{ + state: attr.ValueStateNull, + } +} + +func NewWeeklyValueUnknown() WeeklyValue { + return WeeklyValue{ + state: attr.ValueStateUnknown, + } +} + +func NewWeeklyValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (WeeklyValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing WeeklyValue Attribute Value", + "While creating a WeeklyValue value, a missing attribute value was detected. "+ + "A WeeklyValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("WeeklyValue Attribute Name (%s) Expected ParamType: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid WeeklyValue Attribute ParamType", + "While creating a WeeklyValue value, an invalid attribute value was detected. "+ + "A WeeklyValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("WeeklyValue Attribute Name (%s) Expected ParamType: %s\n", name, attributeType.String())+ + fmt.Sprintf("WeeklyValue Attribute Name (%s) Given ParamType: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra WeeklyValue Attribute Value", + "While creating a WeeklyValue value, an extra attribute value was detected. "+ + "A WeeklyValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra WeeklyValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewWeeklyValueUnknown(), diags + } + + daysAttribute, ok := attributes["days"] + + if !ok { + diags.AddError( + "Attribute Missing", + `days is missing from object`) + + return NewWeeklyValueUnknown(), diags + } + + daysVal, ok := daysAttribute.(basetypes.ListValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`days expected to be basetypes.ListValue, was: %T`, daysAttribute)) + } + + timeAttribute, ok := attributes["time"] + + if !ok { + diags.AddError( + "Attribute Missing", + `time is missing from object`) + + return NewWeeklyValueUnknown(), diags + } + + timeVal, ok := timeAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`time expected to be basetypes.StringValue, was: %T`, timeAttribute)) + } + + if diags.HasError() { + return NewWeeklyValueUnknown(), diags + } + + return WeeklyValue{ + Days: daysVal, + Time: timeVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewWeeklyValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) WeeklyValue { + object, diags := NewWeeklyValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewWeeklyValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t WeeklyType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewWeeklyValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewWeeklyValueUnknown(), nil + } + + if in.IsNull() { + return NewWeeklyValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewWeeklyValueMust(t.AttrTypes, attributes), nil +} + +func (t WeeklyType) ValueType(ctx context.Context) attr.Value { + return WeeklyValue{} +} + +var _ basetypes.ObjectValuable = WeeklyValue{} + +type WeeklyValue struct { + Days basetypes.ListValue `tfsdk:"days"` + Time basetypes.StringValue `tfsdk:"time"` + state attr.ValueState +} + +func (v WeeklyValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 2) + + var val tftypes.Value + var err error + + attrTypes["days"] = basetypes.ListType{ + ElemType: types.Int64Type, + }.TerraformType(ctx) + attrTypes["time"] = basetypes.StringType{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 2) + + val, err = v.Days.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["days"] = val + + val, err = v.Time.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["time"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v WeeklyValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v WeeklyValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v WeeklyValue) String() string { + return "WeeklyValue" +} + +func (v WeeklyValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "days": basetypes.ListType{ + ElemType: types.Int64Type, + }, + "time": basetypes.StringType{}, + }, + map[string]attr.Value{ + "days": v.Days, + "time": v.Time, + }) + + return objVal, diags +} + +func (v WeeklyValue) Equal(o attr.Value) bool { + other, ok := o.(WeeklyValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.Days.Equal(other.Days) { + return false + } + + if !v.Time.Equal(other.Time) { + return false + } + + return true +} + +func (v WeeklyValue) Type(ctx context.Context) attr.Type { + return WeeklyType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v WeeklyValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "days": basetypes.ListType{ + ElemType: types.Int64Type, + }, + "time": basetypes.StringType{}, + } +} + +var _ basetypes.ObjectTypable = ReenrollmentStatusType{} + +type ReenrollmentStatusType struct { + basetypes.ObjectType +} + +func (t ReenrollmentStatusType) Equal(o attr.Type) bool { + other, ok := o.(ReenrollmentStatusType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t ReenrollmentStatusType) String() string { + return "ReenrollmentStatusType" +} + +func (t ReenrollmentStatusType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + agentIdAttribute, ok := attributes["agent_id"] + + if !ok { + diags.AddError( + "Attribute Missing", + `agent_id is missing from object`) + + return nil, diags + } + + agentIdVal, ok := agentIdAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`agent_id expected to be basetypes.StringValue, was: %T`, agentIdAttribute)) + } + + customAliasAllowedAttribute, ok := attributes["custom_alias_allowed"] + + if !ok { + diags.AddError( + "Attribute Missing", + `custom_alias_allowed is missing from object`) + + return nil, diags + } + + customAliasAllowedVal, ok := customAliasAllowedAttribute.(basetypes.Int64Value) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`custom_alias_allowed expected to be basetypes.Int64Value, was: %T`, customAliasAllowedAttribute)) + } + + dataAttribute, ok := attributes["data"] + + if !ok { + diags.AddError( + "Attribute Missing", + `data is missing from object`) + + return nil, diags + } + + dataVal, ok := dataAttribute.(basetypes.BoolValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`data expected to be basetypes.BoolValue, was: %T`, dataAttribute)) + } + + entryParametersAttribute, ok := attributes["entry_parameters"] + + if !ok { + diags.AddError( + "Attribute Missing", + `entry_parameters is missing from object`) + + return nil, diags + } + + entryParametersVal, ok := entryParametersAttribute.(basetypes.ListValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`entry_parameters expected to be basetypes.ListValue, was: %T`, entryParametersAttribute)) + } + + jobPropertiesAttribute, ok := attributes["job_properties"] + + if !ok { + diags.AddError( + "Attribute Missing", + `job_properties is missing from object`) + + return nil, diags + } + + jobPropertiesVal, ok := jobPropertiesAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`job_properties expected to be basetypes.StringValue, was: %T`, jobPropertiesAttribute)) + } + + messageAttribute, ok := attributes["message"] + + if !ok { + diags.AddError( + "Attribute Missing", + `message is missing from object`) + + return nil, diags + } + + messageVal, ok := messageAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`message expected to be basetypes.StringValue, was: %T`, messageAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return ReenrollmentStatusValue{ + AgentId: agentIdVal, + CustomAliasAllowed: customAliasAllowedVal, + Data: dataVal, + EntryParameters: entryParametersVal, + JobProperties: jobPropertiesVal, + Message: messageVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewReenrollmentStatusValueNull() ReenrollmentStatusValue { + return ReenrollmentStatusValue{ + state: attr.ValueStateNull, + } +} + +func NewReenrollmentStatusValueUnknown() ReenrollmentStatusValue { + return ReenrollmentStatusValue{ + state: attr.ValueStateUnknown, + } +} + +func NewReenrollmentStatusValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (ReenrollmentStatusValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing ReenrollmentStatusValue Attribute Value", + "While creating a ReenrollmentStatusValue value, a missing attribute value was detected. "+ + "A ReenrollmentStatusValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("ReenrollmentStatusValue Attribute Name (%s) Expected ParamType: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid ReenrollmentStatusValue Attribute ParamType", + "While creating a ReenrollmentStatusValue value, an invalid attribute value was detected. "+ + "A ReenrollmentStatusValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("ReenrollmentStatusValue Attribute Name (%s) Expected ParamType: %s\n", name, attributeType.String())+ + fmt.Sprintf("ReenrollmentStatusValue Attribute Name (%s) Given ParamType: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra ReenrollmentStatusValue Attribute Value", + "While creating a ReenrollmentStatusValue value, an extra attribute value was detected. "+ + "A ReenrollmentStatusValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra ReenrollmentStatusValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewReenrollmentStatusValueUnknown(), diags + } + + agentIdAttribute, ok := attributes["agent_id"] + + if !ok { + diags.AddError( + "Attribute Missing", + `agent_id is missing from object`) + + return NewReenrollmentStatusValueUnknown(), diags + } + + agentIdVal, ok := agentIdAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`agent_id expected to be basetypes.StringValue, was: %T`, agentIdAttribute)) + } + + customAliasAllowedAttribute, ok := attributes["custom_alias_allowed"] + + if !ok { + diags.AddError( + "Attribute Missing", + `custom_alias_allowed is missing from object`) + + return NewReenrollmentStatusValueUnknown(), diags + } + + customAliasAllowedVal, ok := customAliasAllowedAttribute.(basetypes.Int64Value) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`custom_alias_allowed expected to be basetypes.Int64Value, was: %T`, customAliasAllowedAttribute)) + } + + dataAttribute, ok := attributes["data"] + + if !ok { + diags.AddError( + "Attribute Missing", + `data is missing from object`) + + return NewReenrollmentStatusValueUnknown(), diags + } + + dataVal, ok := dataAttribute.(basetypes.BoolValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`data expected to be basetypes.BoolValue, was: %T`, dataAttribute)) + } + + entryParametersAttribute, ok := attributes["entry_parameters"] + + if !ok { + diags.AddError( + "Attribute Missing", + `entry_parameters is missing from object`) + + return NewReenrollmentStatusValueUnknown(), diags + } + + entryParametersVal, ok := entryParametersAttribute.(basetypes.ListValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`entry_parameters expected to be basetypes.ListValue, was: %T`, entryParametersAttribute)) + } + + jobPropertiesAttribute, ok := attributes["job_properties"] + + if !ok { + diags.AddError( + "Attribute Missing", + `job_properties is missing from object`) + + return NewReenrollmentStatusValueUnknown(), diags + } + + jobPropertiesVal, ok := jobPropertiesAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`job_properties expected to be basetypes.StringValue, was: %T`, jobPropertiesAttribute)) + } + + messageAttribute, ok := attributes["message"] + + if !ok { + diags.AddError( + "Attribute Missing", + `message is missing from object`) + + return NewReenrollmentStatusValueUnknown(), diags + } + + messageVal, ok := messageAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`message expected to be basetypes.StringValue, was: %T`, messageAttribute)) + } + + if diags.HasError() { + return NewReenrollmentStatusValueUnknown(), diags + } + + return ReenrollmentStatusValue{ + AgentId: agentIdVal, + CustomAliasAllowed: customAliasAllowedVal, + Data: dataVal, + EntryParameters: entryParametersVal, + JobProperties: jobPropertiesVal, + Message: messageVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewReenrollmentStatusValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) ReenrollmentStatusValue { + object, diags := NewReenrollmentStatusValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewReenrollmentStatusValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t ReenrollmentStatusType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewReenrollmentStatusValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewReenrollmentStatusValueUnknown(), nil + } + + if in.IsNull() { + return NewReenrollmentStatusValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewReenrollmentStatusValueMust(t.AttrTypes, attributes), nil +} + +func (t ReenrollmentStatusType) ValueType(ctx context.Context) attr.Value { + return ReenrollmentStatusValue{} +} + +var _ basetypes.ObjectValuable = ReenrollmentStatusValue{} + +type ReenrollmentStatusValue struct { + AgentId basetypes.StringValue `tfsdk:"agent_id"` + CustomAliasAllowed basetypes.Int64Value `tfsdk:"custom_alias_allowed"` + Data basetypes.BoolValue `tfsdk:"data"` + EntryParameters basetypes.ListValue `tfsdk:"entry_parameters"` + JobProperties basetypes.StringValue `tfsdk:"job_properties"` + Message basetypes.StringValue `tfsdk:"message"` + state attr.ValueState +} + +func (v ReenrollmentStatusValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 6) + + var val tftypes.Value + var err error + + attrTypes["agent_id"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["custom_alias_allowed"] = basetypes.Int64Type{}.TerraformType(ctx) + attrTypes["data"] = basetypes.BoolType{}.TerraformType(ctx) + attrTypes["entry_parameters"] = basetypes.ListType{ + ElemType: EntryParametersValue{}.Type(ctx), + }.TerraformType(ctx) + attrTypes["job_properties"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["message"] = basetypes.StringType{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 6) + + val, err = v.AgentId.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["agent_id"] = val + + val, err = v.CustomAliasAllowed.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["custom_alias_allowed"] = val + + val, err = v.Data.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["data"] = val + + val, err = v.EntryParameters.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["entry_parameters"] = val + + val, err = v.JobProperties.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["job_properties"] = val + + val, err = v.Message.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["message"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v ReenrollmentStatusValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v ReenrollmentStatusValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v ReenrollmentStatusValue) String() string { + return "ReenrollmentStatusValue" +} + +func (v ReenrollmentStatusValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + entryParameters := types.ListValueMust( + EntryParametersType{ + basetypes.ObjectType{ + AttrTypes: EntryParametersValue{}.AttributeTypes(ctx), + }, + }, + v.EntryParameters.Elements(), + ) + + if v.EntryParameters.IsNull() { + entryParameters = types.ListNull( + EntryParametersType{ + basetypes.ObjectType{ + AttrTypes: EntryParametersValue{}.AttributeTypes(ctx), + }, + }, + ) + } + + if v.EntryParameters.IsUnknown() { + entryParameters = types.ListUnknown( + EntryParametersType{ + basetypes.ObjectType{ + AttrTypes: EntryParametersValue{}.AttributeTypes(ctx), + }, + }, + ) + } + + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "agent_id": basetypes.StringType{}, + "custom_alias_allowed": basetypes.Int64Type{}, + "data": basetypes.BoolType{}, + "entry_parameters": basetypes.ListType{ + ElemType: EntryParametersValue{}.Type(ctx), + }, + "job_properties": basetypes.StringType{}, + "message": basetypes.StringType{}, + }, + map[string]attr.Value{ + "agent_id": v.AgentId, + "custom_alias_allowed": v.CustomAliasAllowed, + "data": v.Data, + "entry_parameters": entryParameters, + "job_properties": v.JobProperties, + "message": v.Message, + }) + + return objVal, diags +} + +func (v ReenrollmentStatusValue) Equal(o attr.Value) bool { + other, ok := o.(ReenrollmentStatusValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.AgentId.Equal(other.AgentId) { + return false + } + + if !v.CustomAliasAllowed.Equal(other.CustomAliasAllowed) { + return false + } + + if !v.Data.Equal(other.Data) { + return false + } + + if !v.EntryParameters.Equal(other.EntryParameters) { + return false + } + + if !v.JobProperties.Equal(other.JobProperties) { + return false + } + + if !v.Message.Equal(other.Message) { + return false + } + + return true +} + +func (v ReenrollmentStatusValue) Type(ctx context.Context) attr.Type { + return ReenrollmentStatusType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v ReenrollmentStatusValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "agent_id": basetypes.StringType{}, + "custom_alias_allowed": basetypes.Int64Type{}, + "data": basetypes.BoolType{}, + "entry_parameters": basetypes.ListType{ + ElemType: EntryParametersValue{}.Type(ctx), + }, + "job_properties": basetypes.StringType{}, + "message": basetypes.StringType{}, + } +} + +var _ basetypes.ObjectTypable = EntryParametersType{} + +type EntryParametersType struct { + basetypes.ObjectType +} + +func (t EntryParametersType) Equal(o attr.Type) bool { + other, ok := o.(EntryParametersType) + + if !ok { + return false + } + + return t.ObjectType.Equal(other.ObjectType) +} + +func (t EntryParametersType) String() string { + return "EntryParametersType" +} + +func (t EntryParametersType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) { + var diags diag.Diagnostics + + attributes := in.Attributes() + + defaultValueAttribute, ok := attributes["default_value"] + + if !ok { + diags.AddError( + "Attribute Missing", + `default_value is missing from object`) + + return nil, diags + } + + defaultValueVal, ok := defaultValueAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`default_value expected to be basetypes.StringValue, was: %T`, defaultValueAttribute)) + } + + dependsOnAttribute, ok := attributes["depends_on"] + + if !ok { + diags.AddError( + "Attribute Missing", + `depends_on is missing from object`) + + return nil, diags + } + + dependsOnVal, ok := dependsOnAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`depends_on expected to be basetypes.StringValue, was: %T`, dependsOnAttribute)) + } + + displayNameAttribute, ok := attributes["display_name"] + + if !ok { + diags.AddError( + "Attribute Missing", + `display_name is missing from object`) + + return nil, diags + } + + displayNameVal, ok := displayNameAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`display_name expected to be basetypes.StringValue, was: %T`, displayNameAttribute)) + } + + nameAttribute, ok := attributes["name"] + + if !ok { + diags.AddError( + "Attribute Missing", + `name is missing from object`) + + return nil, diags + } + + nameVal, ok := nameAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`name expected to be basetypes.StringValue, was: %T`, nameAttribute)) + } + + optionsAttribute, ok := attributes["options"] + + if !ok { + diags.AddError( + "Attribute Missing", + `options is missing from object`) + + return nil, diags + } + + optionsVal, ok := optionsAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`options expected to be basetypes.StringValue, was: %T`, optionsAttribute)) + } + + requiredWhenAttribute, ok := attributes["required_when"] + + if !ok { + diags.AddError( + "Attribute Missing", + `required_when is missing from object`) + + return nil, diags + } + + requiredWhenVal, ok := requiredWhenAttribute.(basetypes.MapValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`required_when expected to be basetypes.MapValue, was: %T`, requiredWhenAttribute)) + } + + storeTypeIdAttribute, ok := attributes["store_type_id"] + + if !ok { + diags.AddError( + "Attribute Missing", + `store_type_id is missing from object`) + + return nil, diags + } + + storeTypeIdVal, ok := storeTypeIdAttribute.(basetypes.Int64Value) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`store_type_id expected to be basetypes.Int64Value, was: %T`, storeTypeIdAttribute)) + } + + typeAttribute, ok := attributes["type"] + + if !ok { + diags.AddError( + "Attribute Missing", + `type is missing from object`) + + return nil, diags + } + + typeVal, ok := typeAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`type expected to be basetypes.StringValue, was: %T`, typeAttribute)) + } + + if diags.HasError() { + return nil, diags + } + + return EntryParametersValue{ + DefaultValue: defaultValueVal, + DependsOn: dependsOnVal, + DisplayName: displayNameVal, + Name: nameVal, + Options: optionsVal, + RequiredWhen: requiredWhenVal, + StoreTypeId: storeTypeIdVal, + ParamType: typeVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewEntryParametersValueNull() EntryParametersValue { + return EntryParametersValue{ + state: attr.ValueStateNull, + } +} + +func NewEntryParametersValueUnknown() EntryParametersValue { + return EntryParametersValue{ + state: attr.ValueStateUnknown, + } +} + +func NewEntryParametersValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (EntryParametersValue, diag.Diagnostics) { + var diags diag.Diagnostics + + // Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521 + ctx := context.Background() + + for name, attributeType := range attributeTypes { + attribute, ok := attributes[name] + + if !ok { + diags.AddError( + "Missing EntryParametersValue Attribute Value", + "While creating a EntryParametersValue value, a missing attribute value was detected. "+ + "A EntryParametersValue must contain values for all attributes, even if null or unknown. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("EntryParametersValue Attribute Name (%s) Expected ParamType: %s", name, attributeType.String()), + ) + + continue + } + + if !attributeType.Equal(attribute.Type(ctx)) { + diags.AddError( + "Invalid EntryParametersValue Attribute ParamType", + "While creating a EntryParametersValue value, an invalid attribute value was detected. "+ + "A EntryParametersValue must use a matching attribute type for the value. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("EntryParametersValue Attribute Name (%s) Expected ParamType: %s\n", name, attributeType.String())+ + fmt.Sprintf("EntryParametersValue Attribute Name (%s) Given ParamType: %s", name, attribute.Type(ctx)), + ) + } + } + + for name := range attributes { + _, ok := attributeTypes[name] + + if !ok { + diags.AddError( + "Extra EntryParametersValue Attribute Value", + "While creating a EntryParametersValue value, an extra attribute value was detected. "+ + "A EntryParametersValue must not contain values beyond the expected attribute types. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + fmt.Sprintf("Extra EntryParametersValue Attribute Name: %s", name), + ) + } + } + + if diags.HasError() { + return NewEntryParametersValueUnknown(), diags + } + + defaultValueAttribute, ok := attributes["default_value"] + + if !ok { + diags.AddError( + "Attribute Missing", + `default_value is missing from object`) + + return NewEntryParametersValueUnknown(), diags + } + + defaultValueVal, ok := defaultValueAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`default_value expected to be basetypes.StringValue, was: %T`, defaultValueAttribute)) + } + + dependsOnAttribute, ok := attributes["depends_on"] + + if !ok { + diags.AddError( + "Attribute Missing", + `depends_on is missing from object`) + + return NewEntryParametersValueUnknown(), diags + } + + dependsOnVal, ok := dependsOnAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`depends_on expected to be basetypes.StringValue, was: %T`, dependsOnAttribute)) + } + + displayNameAttribute, ok := attributes["display_name"] + + if !ok { + diags.AddError( + "Attribute Missing", + `display_name is missing from object`) + + return NewEntryParametersValueUnknown(), diags + } + + displayNameVal, ok := displayNameAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`display_name expected to be basetypes.StringValue, was: %T`, displayNameAttribute)) + } + + nameAttribute, ok := attributes["name"] + + if !ok { + diags.AddError( + "Attribute Missing", + `name is missing from object`) + + return NewEntryParametersValueUnknown(), diags + } + + nameVal, ok := nameAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`name expected to be basetypes.StringValue, was: %T`, nameAttribute)) + } + + optionsAttribute, ok := attributes["options"] + + if !ok { + diags.AddError( + "Attribute Missing", + `options is missing from object`) + + return NewEntryParametersValueUnknown(), diags + } + + optionsVal, ok := optionsAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`options expected to be basetypes.StringValue, was: %T`, optionsAttribute)) + } + + requiredWhenAttribute, ok := attributes["required_when"] + + if !ok { + diags.AddError( + "Attribute Missing", + `required_when is missing from object`) + + return NewEntryParametersValueUnknown(), diags + } + + requiredWhenVal, ok := requiredWhenAttribute.(basetypes.MapValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`required_when expected to be basetypes.MapValue, was: %T`, requiredWhenAttribute)) + } + + storeTypeIdAttribute, ok := attributes["store_type_id"] + + if !ok { + diags.AddError( + "Attribute Missing", + `store_type_id is missing from object`) + + return NewEntryParametersValueUnknown(), diags + } + + storeTypeIdVal, ok := storeTypeIdAttribute.(basetypes.Int64Value) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`store_type_id expected to be basetypes.Int64Value, was: %T`, storeTypeIdAttribute)) + } + + typeAttribute, ok := attributes["type"] + + if !ok { + diags.AddError( + "Attribute Missing", + `type is missing from object`) + + return NewEntryParametersValueUnknown(), diags + } + + typeVal, ok := typeAttribute.(basetypes.StringValue) + + if !ok { + diags.AddError( + "Attribute Wrong ParamType", + fmt.Sprintf(`type expected to be basetypes.StringValue, was: %T`, typeAttribute)) + } + + if diags.HasError() { + return NewEntryParametersValueUnknown(), diags + } + + return EntryParametersValue{ + DefaultValue: defaultValueVal, + DependsOn: dependsOnVal, + DisplayName: displayNameVal, + Name: nameVal, + Options: optionsVal, + RequiredWhen: requiredWhenVal, + StoreTypeId: storeTypeIdVal, + ParamType: typeVal, + state: attr.ValueStateKnown, + }, diags +} + +func NewEntryParametersValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) EntryParametersValue { + object, diags := NewEntryParametersValue(attributeTypes, attributes) + + if diags.HasError() { + // This could potentially be added to the diag package. + diagsStrings := make([]string, 0, len(diags)) + + for _, diagnostic := range diags { + diagsStrings = append(diagsStrings, fmt.Sprintf( + "%s | %s | %s", + diagnostic.Severity(), + diagnostic.Summary(), + diagnostic.Detail())) + } + + panic("NewEntryParametersValueMust received error(s): " + strings.Join(diagsStrings, "\n")) + } + + return object +} + +func (t EntryParametersType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) { + if in.Type() == nil { + return NewEntryParametersValueNull(), nil + } + + if !in.Type().Equal(t.TerraformType(ctx)) { + return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type()) + } + + if !in.IsKnown() { + return NewEntryParametersValueUnknown(), nil + } + + if in.IsNull() { + return NewEntryParametersValueNull(), nil + } + + attributes := map[string]attr.Value{} + + val := map[string]tftypes.Value{} + + err := in.As(&val) + + if err != nil { + return nil, err + } + + for k, v := range val { + a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v) + + if err != nil { + return nil, err + } + + attributes[k] = a + } + + return NewEntryParametersValueMust(t.AttrTypes, attributes), nil +} + +func (t EntryParametersType) ValueType(ctx context.Context) attr.Value { + return EntryParametersValue{} +} + +var _ basetypes.ObjectValuable = EntryParametersValue{} + +type EntryParametersValue struct { + DefaultValue basetypes.StringValue `tfsdk:"default_value"` + DependsOn basetypes.StringValue `tfsdk:"depends_on"` + DisplayName basetypes.StringValue `tfsdk:"display_name"` + Name basetypes.StringValue `tfsdk:"name"` + Options basetypes.StringValue `tfsdk:"options"` + RequiredWhen basetypes.MapValue `tfsdk:"required_when"` + StoreTypeId basetypes.Int64Value `tfsdk:"store_type_id"` + ParamType basetypes.StringValue `tfsdk:"type"` + state attr.ValueState +} + +func (v EntryParametersValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + attrTypes := make(map[string]tftypes.Type, 8) + + var val tftypes.Value + var err error + + attrTypes["default_value"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["depends_on"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["display_name"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["name"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["options"] = basetypes.StringType{}.TerraformType(ctx) + attrTypes["required_when"] = basetypes.MapType{ + ElemType: types.BoolType, + }.TerraformType(ctx) + attrTypes["store_type_id"] = basetypes.Int64Type{}.TerraformType(ctx) + attrTypes["type"] = basetypes.StringType{}.TerraformType(ctx) + + objectType := tftypes.Object{AttributeTypes: attrTypes} + + switch v.state { + case attr.ValueStateKnown: + vals := make(map[string]tftypes.Value, 8) + + val, err = v.DefaultValue.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["default_value"] = val + + val, err = v.DependsOn.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["depends_on"] = val + + val, err = v.DisplayName.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["display_name"] = val + + val, err = v.Name.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["name"] = val + + val, err = v.Options.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["options"] = val + + val, err = v.RequiredWhen.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["required_when"] = val + + val, err = v.StoreTypeId.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["store_type_id"] = val + + val, err = v.ParamType.ToTerraformValue(ctx) + + if err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + vals["type"] = val + + if err := tftypes.ValidateValue(objectType, vals); err != nil { + return tftypes.NewValue(objectType, tftypes.UnknownValue), err + } + + return tftypes.NewValue(objectType, vals), nil + case attr.ValueStateNull: + return tftypes.NewValue(objectType, nil), nil + case attr.ValueStateUnknown: + return tftypes.NewValue(objectType, tftypes.UnknownValue), nil + default: + panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state)) + } +} + +func (v EntryParametersValue) IsNull() bool { + return v.state == attr.ValueStateNull +} + +func (v EntryParametersValue) IsUnknown() bool { + return v.state == attr.ValueStateUnknown +} + +func (v EntryParametersValue) String() string { + return "EntryParametersValue" +} + +func (v EntryParametersValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) { + objVal, diags := types.ObjectValue( + map[string]attr.Type{ + "default_value": basetypes.StringType{}, + "depends_on": basetypes.StringType{}, + "display_name": basetypes.StringType{}, + "name": basetypes.StringType{}, + "options": basetypes.StringType{}, + "required_when": basetypes.MapType{ + ElemType: types.BoolType, + }, + "store_type_id": basetypes.Int64Type{}, + "type": basetypes.StringType{}, + }, + map[string]attr.Value{ + "default_value": v.DefaultValue, + "depends_on": v.DependsOn, + "display_name": v.DisplayName, + "name": v.Name, + "options": v.Options, + "required_when": v.RequiredWhen, + "store_type_id": v.StoreTypeId, + "type": v.ParamType, + }) + + return objVal, diags +} + +func (v EntryParametersValue) Equal(o attr.Value) bool { + other, ok := o.(EntryParametersValue) + + if !ok { + return false + } + + if v.state != other.state { + return false + } + + if v.state != attr.ValueStateKnown { + return true + } + + if !v.DefaultValue.Equal(other.DefaultValue) { + return false + } + + if !v.DependsOn.Equal(other.DependsOn) { + return false + } + + if !v.DisplayName.Equal(other.DisplayName) { + return false + } + + if !v.Name.Equal(other.Name) { + return false + } + + if !v.Options.Equal(other.Options) { + return false + } + + if !v.RequiredWhen.Equal(other.RequiredWhen) { + return false + } + + if !v.StoreTypeId.Equal(other.StoreTypeId) { + return false + } + + if !v.ParamType.Equal(other.ParamType) { + return false + } + + return true +} + +func (v EntryParametersValue) Type(ctx context.Context) attr.Type { + return EntryParametersType{ + basetypes.ObjectType{ + AttrTypes: v.AttributeTypes(ctx), + }, + } +} + +func (v EntryParametersValue) AttributeTypes(ctx context.Context) map[string]attr.Type { + return map[string]attr.Type{ + "default_value": basetypes.StringType{}, + "depends_on": basetypes.StringType{}, + "display_name": basetypes.StringType{}, + "name": basetypes.StringType{}, + "options": basetypes.StringType{}, + "required_when": basetypes.MapType{ + ElemType: types.BoolType, + }, + "store_type_id": basetypes.Int64Type{}, + "type": basetypes.StringType{}, + } +} + +var _ datasource.DataSource = &CertificateStoreDataSource{} + +func NewCertificateStoreDataSourceDataSource() datasource.DataSource { + return &CertificateStoreDataSource{} +} + +// AgentDataSource defines the data source implementation. +type CertificateStoreDataSource struct { + provider *Provider + client *kfc.APIClient +} + +func (d *CertificateStoreDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = "keyfactor_certificate_store" +} + +func (d *CertificateStoreDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "agent_assigned": schema.BoolAttribute{ + Computed: true, + Description: "A Boolean that indicates whether there is an orchestrator assigned to this certificate store (true) or not (false).", + MarkdownDescription: "A Boolean that indicates whether there is an orchestrator assigned to this certificate store (true) or not (false).", + }, + "agent_id": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the Keyfactor Command GUID of the orchestrator for this store.", + MarkdownDescription: "A string indicating the Keyfactor Command GUID of the orchestrator for this store.", + }, + "approved": schema.BoolAttribute{ + Computed: true, + Description: "A Boolean that indicates whether a certificate store is approved (true) or not (false). If a certificate store is approved, it can be used and updated. A certificate store that has been discovered using the discover feature but not yet marked as approved will be false here.", + MarkdownDescription: "A Boolean that indicates whether a certificate store is approved (true) or not (false). If a certificate store is approved, it can be used and updated. A certificate store that has been discovered using the discover feature but not yet marked as approved will be false here.", + }, + "cert_store_inventory_job_id": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the GUID that identifies the inventory job for the certificate store in the Keyfactor Command database. This will be null if an inventory schedule is not set for the certificate store.", + MarkdownDescription: "A string indicating the GUID that identifies the inventory job for the certificate store in the Keyfactor Command database. This will be null if an inventory schedule is not set for the certificate store.", + }, + "cert_store_type": schema.Int64Attribute{ + Computed: true, + Description: "An integer indicating the ID of the certificate store type, as defined in Keyfactor Command, for this certificate store. (0-Javakeystore,2-PEMFile, 3-F5SSLProfiles,4-IISRoots, 5-NetScaler, 6-IISPersonal, 7-F5WebServer, 8-IISRevoked, 9-F5WebServerREST, 10-F5SSLProfilesREST, 11-F5CABundlesREST, 100-AmazonWebServices, 101-FileTransferProtocol)", + MarkdownDescription: "An integer indicating the ID of the certificate store type, as defined in Keyfactor Command, for this certificate store. (0-Javakeystore,2-PEMFile, 3-F5SSLProfiles,4-IISRoots, 5-NetScaler, 6-IISPersonal, 7-F5WebServer, 8-IISRevoked, 9-F5WebServerREST, 10-F5SSLProfilesREST, 11-F5CABundlesREST, 100-AmazonWebServices, 101-FileTransferProtocol)", + }, + "client_machine": schema.StringAttribute{ + Required: true, + Description: "The string value of the client machine. The value for this will vary depending on the certificate store type. For example, for a Java keystore or an F5 device, it is the hostname of the machine on which the store is located, but for an Amazon Web Services store, it is the FQDN of the Keyfactor Command Windows Orchestrator. See Adding or Modifying a Certificate Store in the Keyfactor Command Reference Guide for more information.", + MarkdownDescription: "The string value of the client machine. The value for this will vary depending on the certificate store type. For example, for a Java keystore or an F5 device, it is the hostname of the machine on which the store is located, but for an Amazon Web Services store, it is the FQDN of the Keyfactor Command Windows Orchestrator. See Adding or Modifying a Certificate Store in the Keyfactor Command Reference Guide for more information.", + }, + "container_id": schema.Int64Attribute{ + Computed: true, + Description: "An integer indicating the ID of the certificate store's associated certificate store container, if applicable (see GET Certificate Store Containers).", + MarkdownDescription: "An integer indicating the ID of the certificate store's associated certificate store container, if applicable (see GET Certificate Store Containers).", + }, + "container_name": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the name of the certificate store's associated container, if applicable.", + MarkdownDescription: "A string indicating the name of the certificate store's associated container, if applicable.", + }, + "create_if_missing": schema.BoolAttribute{ + Computed: true, + Description: "A Boolean that indicates whether a new certificate store should be created with the information provided (true) or not (false). This option is only valid for Java keystores and any custom certificate store types you have defined to support this functionality.", + MarkdownDescription: "A Boolean that indicates whether a new certificate store should be created with the information provided (true) or not (false). This option is only valid for Java keystores and any custom certificate store types you have defined to support this functionality.", + }, + "display_name": schema.StringAttribute{ + Computed: true, + Description: "A string indicating the display name of the certificate store.", + MarkdownDescription: "A string indicating the display name of the certificate store.", + }, + "id": schema.StringAttribute{ + Computed: true, + Description: "Keyfactor identifier (GUID) of the certificate store", + MarkdownDescription: "Keyfactor identifier (GUID) of the certificate store", + }, + "inventory_schedule": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "daily": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "time": schema.StringAttribute{ + Computed: true, + }, + }, + CustomType: DailyType{ + ObjectType: types.ObjectType{ + AttrTypes: DailyValue{}.AttributeTypes(ctx), + }, + }, + Computed: true, + }, + "exactly_once": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "time": schema.StringAttribute{ + Computed: true, + }, + }, + CustomType: ExactlyOnceType{ + ObjectType: types.ObjectType{ + AttrTypes: ExactlyOnceValue{}.AttributeTypes(ctx), + }, + }, + Computed: true, + }, + "immediate": schema.BoolAttribute{ + Computed: true, + }, + "interval": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "minutes": schema.Int64Attribute{ + Computed: true, + }, + }, + CustomType: IntervalType{ + ObjectType: types.ObjectType{ + AttrTypes: IntervalValue{}.AttributeTypes(ctx), + }, + }, + Computed: true, + }, + "monthly": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "day": schema.Int64Attribute{ + Computed: true, + }, + "time": schema.StringAttribute{ + Computed: true, + }, + }, + CustomType: MonthlyType{ + ObjectType: types.ObjectType{ + AttrTypes: MonthlyValue{}.AttributeTypes(ctx), + }, + }, + Computed: true, + }, + "weekly": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "days": schema.ListAttribute{ + ElementType: types.Int64Type, + Computed: true, + }, + "time": schema.StringAttribute{ + Computed: true, + }, + }, + CustomType: WeeklyType{ + ObjectType: types.ObjectType{ + AttrTypes: WeeklyValue{}.AttributeTypes(ctx), + }, + }, + Computed: true, + }, + }, + CustomType: InventoryScheduleType{ + ObjectType: types.ObjectType{ + AttrTypes: InventoryScheduleValue{}.AttributeTypes(ctx), + }, + }, + Computed: true, + }, + "properties": schema.StringAttribute{ + Computed: true, + Description: "Some types of certificate stores have additional properties that are stored in this parameter. The data is stored in a series of, typically, key value pairs that define the property name and value (see GET Certificate Store Types for more information).\n\nAs of Keyfactor Command v10, this parameter is used to store certificate store server usernames, server passwords, and the UseSSL flag. Built-in certificate stores that typically require configuration of certificate store server parameters include NetScaler and F5 stores. The legacy methods for managing certificate store server credentials have been deprecated but are retained for backwards compatiblity. For more information, see POST Certificate Stores Server.\n\nWhen reading this field, the values are returned as simple key value pairs, with the values being individual values. When writing, the values are specified as objects, though they are typically single values.\n", + MarkdownDescription: "Some types of certificate stores have additional properties that are stored in this parameter. The data is stored in a series of, typically, key value pairs that define the property name and value (see GET Certificate Store Types for more information).\n\nAs of Keyfactor Command v10, this parameter is used to store certificate store server usernames, server passwords, and the UseSSL flag. Built-in certificate stores that typically require configuration of certificate store server parameters include NetScaler and F5 stores. The legacy methods for managing certificate store server credentials have been deprecated but are retained for backwards compatiblity. For more information, see POST Certificate Stores Server.\n\nWhen reading this field, the values are returned as simple key value pairs, with the values being individual values. When writing, the values are specified as objects, though they are typically single values.\n", + }, + "reenrollment_status": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "agent_id": schema.StringAttribute{ + Computed: true, + }, + "custom_alias_allowed": schema.Int64Attribute{ + Computed: true, + }, + "data": schema.BoolAttribute{ + Computed: true, + }, + "entry_parameters": schema.ListNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "default_value": schema.StringAttribute{ + Computed: true, + }, + "depends_on": schema.StringAttribute{ + Computed: true, + }, + "display_name": schema.StringAttribute{ + Computed: true, + }, + "name": schema.StringAttribute{ + Computed: true, + }, + "options": schema.StringAttribute{ + Computed: true, + }, + "required_when": schema.MapAttribute{ + ElementType: types.BoolType, + Computed: true, + }, + "store_type_id": schema.Int64Attribute{ + Computed: true, + }, + "type": schema.StringAttribute{ + Computed: true, + }, + }, + CustomType: EntryParametersType{ + ObjectType: types.ObjectType{ + AttrTypes: EntryParametersValue{}.AttributeTypes(ctx), + }, + }, + }, + Computed: true, + }, + "job_properties": schema.StringAttribute{ + Computed: true, + }, + "message": schema.StringAttribute{ + Computed: true, + }, + }, + CustomType: ReenrollmentStatusType{ + ObjectType: types.ObjectType{ + AttrTypes: ReenrollmentStatusValue{}.AttributeTypes(ctx), + }, + }, + Computed: true, + }, + "set_new_password_allowed": schema.BoolAttribute{ + Computed: true, + Description: "A Boolean that indicates whether the store password can be changed (true) or not (false).", + MarkdownDescription: "A Boolean that indicates whether the store password can be changed (true) or not (false).", + }, + "storepath": schema.StringAttribute{ + Required: true, + Description: "A string indicating the path to the certificate store on the target. The format for this path will vary depending on the certificate store type. For example, for a Java keystore, this will be a file path (e.g. /opt/myapp/store.jks), but for an F5 device, this will be a partition name on the device (e.g. Common). See Adding or Modifying a Certificate Store in the Keyfactor Command Reference Guide for more information. The maximum number of characters supported in this field is 722.", + MarkdownDescription: "A string indicating the path to the certificate store on the target. The format for this path will vary depending on the certificate store type. For example, for a Java keystore, this will be a file path (e.g. /opt/myapp/store.jks), but for an F5 device, this will be a partition name on the device (e.g. Common). See Adding or Modifying a Certificate Store in the Keyfactor Command Reference Guide for more information. The maximum number of characters supported in this field is 722.", + }, + }, + } +} + +func (d *CertificateStoreDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*kfc.APIClient) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *kfc.APIClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + d.client = client +} + +func (d *CertificateStoreDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data KeyfactorCertificateStoreModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + if data.Storepath.IsNull() || data.ClientMachine.IsNull() { + resp.Diagnostics.AddError( + "Invalid Certificate Store Configuration", + "store_path and client_machine are required fields", + ) + return + } + + ctx = tflog.SetField(ctx, "store_path", data.Storepath.ValueString()) + ctx = tflog.SetField(ctx, "client_machine", data.ClientMachine.ValueString()) + tflog.Info(ctx, "Read called on certificate store data source") + + //storesResp, httpResp, httpRespErr := d.client.CertificateStoreApi. + // + //if httpRespErr != nil { + // resp.Diagnostics.AddError( + // "Certificate Store Read Error", + // fmt.Sprintf("Error querying Keyfactor Command for certificate store %s-%s: %s", data.ClientMachine.String(), data.Storepath.String(), httpRespErr.Error()), + // ) + // return + //} else if httpResp != nil && httpResp.StatusCode != 200 { + // resp.Diagnostics.AddError( + // "Certificate Store Read Error", + // fmt.Sprintf("Error querying Keyfactor Command for certificate store %s-%s: %s", data.ClientMachine.String(), data.Storepath.String(), httpResp.Status), + // ) + // return + //} + // + //if storesResp == nil { + // resp.Diagnostics.AddError( + // "Certificate Store Not Found Error", + // fmt.Sprintf("No certificate store found with client machine %s and store path %s", data.ClientMachine.String(), data.Storepath.String()), + // ) + // return + //} + + tflog.Info(ctx, "Setting certificate store data source attributes") + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/v3/command/data_source_keyfactor_certificate_test.go b/internal/provider/v3/command/data_source_keyfactor_certificate_test.go new file mode 100644 index 00000000..e29cd3e5 --- /dev/null +++ b/internal/provider/v3/command/data_source_keyfactor_certificate_test.go @@ -0,0 +1,239 @@ +package command + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "os" + "strconv" + "testing" +) + +func TestAccKeyfactorCertificateDataSource(t *testing.T) { + var resourceName = fmt.Sprintf("data.%s.test", "keyfactor_certificate") + var cID = os.Getenv("TEST_CERTIFICATE_ID") + var colID = os.Getenv("TEST_CERTIFICATE_COLLECTION_ID") + var cCN = os.Getenv("TEST_CERTIFICATE_CN") + var cTP = os.Getenv("TEST_CERTIFICATE_THUMBPRINT") + var caID = os.Getenv("TEST_CERTIFICATE_AUTHORITY_CERT_ID") + var password = os.Getenv("TEST_CERTIFICATE_PASSWORD") + if password == "" { + password = generatePassword(DefaultPasswdLength, DefaultMinSpecialChar, DefaultMinNum, DefaultMinUpperCase) + } + if cID == "" { + cID = "1" + } + if caID == "" { + caID = "1" + } + if cCN == "" { + cCN = "SANity Check" + } + if cTP == "" { + t.Log("TEST_CERTIFICATE_THUMBPRINT is not set, skipping TestAccKeyfactorCertificateDataSource") + } + if colID == "" { + colID = "0" + } + colIDInt, _ := strconv.Atoi(colID) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + // Test lookup by ID + Config: testAccDataSourceKeyfactorCertificateBasic(t, cID, password), + Check: certTestValidateAll(resourceName), + }, + { + // Test lookup by empty ID and password + Config: testAccDataSourceKeyfactorCertificateBasic(t, "", ""), + Check: certTestValidateAll(resourceName), + ExpectError: emptyCertRequestErrRegex, + }, + { + // Test lookup by ID w/ short password + Config: testAccDataSourceKeyfactorCertificateBasic(t, cID, "hi"), + Check: certTestValidateAll(resourceName), + }, + { + // Test lookup containing collection ID + // TODO: This needs to have limited access to properly test + Config: testAccDataSourceKeyfactorCertificateCollectionId(t, cID, password, colIDInt), + Check: certTestValidateAll(resourceName), + }, + { + // Test lookup containing invalid collection ID + // TODO: This needs to have limited access to properly test + Config: testAccDataSourceKeyfactorCertificateCollectionId(t, cID, password, 9999), + Check: certTestValidateAll(resourceName), + }, + { + // Test lookup by CN + Config: testAccDataSourceKeyfactorCertificateBasic(t, cCN, password), + Check: certTestValidateAll(resourceName), + }, + { + // Test lookup by thumbprint + Config: testAccDataSourceKeyfactorCertificateBasic(t, cTP, password), + Check: certTestValidateAll(resourceName), + }, + { + // Test CA cert lookup by thumbprint + Config: testAccDataSourceKeyfactorCertificateBasic(t, caID, ""), + Check: certTestValidateCACert(resourceName, caID), + }, + }, + }) +} + +func testAccDataSourceKeyfactorCertificateBasic(t *testing.T, resourceId string, password string) string { + output := fmt.Sprintf(` + data "keyfactor_certificate" "test" { + identifier = "%s" + key_password = "%s" + } + `, resourceId, password) + t.Logf("%s", output) + return output +} + +func testAccDataSourceKeyfactorCertificateCollectionId(t *testing.T, resourceId string, password string, collectionId int) string { + output := fmt.Sprintf(` + data "keyfactor_certificate" "test" { + identifier = "%s" + key_password = "%s" + collection_id = "%d" + } + `, resourceId, password, collectionId) + t.Logf("%s", output) + return output +} + +func certTestValidateAll(resourceName string) resource.TestCheckFunc { + return resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "archived_key"), + resource.TestCheckResourceAttrSet(resourceName, "ca_record_id"), + resource.TestCheckResourceAttrSet(resourceName, "ca_row_index"), + resource.TestCheckResourceAttrSet(resourceName, "cert_state"), + resource.TestCheckResourceAttrSet(resourceName, "certificate_authority"), + resource.TestCheckResourceAttrSet(resourceName, "certificate_authority_id"), + resource.TestCheckResourceAttrSet(resourceName, "certificate_chain"), + resource.TestCheckResourceAttrSet(resourceName, "certificate_id"), + resource.TestCheckResourceAttrSet(resourceName, "certificate_key_id"), + resource.TestCheckResourceAttrSet(resourceName, "certificate_pem"), + resource.TestCheckResourceAttrSet(resourceName, "certificate_template"), + //resource.TestCheckResourceAttrSet(resourceName, "collection_id"), + resource.TestCheckResourceAttrSet(resourceName, "command_request_id"), + resource.TestCheckResourceAttrSet(resourceName, "content_bytes"), + resource.TestCheckResourceAttrSet(resourceName, "crl_distribution_point.#"), + resource.TestCheckResourceAttrSet(resourceName, "curve"), + resource.TestCheckResourceAttrSet(resourceName, "detailed_key_usage.%"), + resource.TestCheckResourceAttrSet(resourceName, "dns_sans.#"), + resource.TestCheckResourceAttrSet(resourceName, "extended_key_usage.#"), + resource.TestCheckResourceAttrSet(resourceName, "has_private_key"), + resource.TestCheckResourceAttrSet(resourceName, "identifier"), + resource.TestCheckResourceAttrSet(resourceName, "import_date"), + //resource.TestCheckResourceAttrSet(resourceName, "include_private_key"), + resource.TestCheckResourceAttrSet(resourceName, "ip_sans.#"), + resource.TestCheckResourceAttrSet(resourceName, "issued_cn"), + resource.TestCheckResourceAttrSet(resourceName, "issued_dn"), + resource.TestCheckResourceAttrSet(resourceName, "issued_email"), + resource.TestCheckResourceAttrSet(resourceName, "issued_ou"), + resource.TestCheckResourceAttrSet(resourceName, "issuer_dn"), + resource.TestCheckResourceAttrSet(resourceName, "key_bits"), + resource.TestCheckResourceAttrSet(resourceName, "key_password"), + resource.TestCheckResourceAttrSet(resourceName, "key_recoverable"), + resource.TestCheckResourceAttrSet(resourceName, "key_type"), + resource.TestCheckResourceAttrSet(resourceName, "key_usage"), + //resource.TestCheckResourceAttrSet(resourceName, "location"), + //resource.TestCheckResourceAttrSet(resourceName, "locations_count"), + resource.TestCheckResourceAttrSet(resourceName, "metadata.%"), + resource.TestCheckResourceAttrSet(resourceName, "not_after"), + resource.TestCheckResourceAttrSet(resourceName, "not_before"), + resource.TestCheckResourceAttrSet(resourceName, "principal_id"), + resource.TestCheckResourceAttrSet(resourceName, "principal_name"), + resource.TestCheckResourceAttrSet(resourceName, "private_key"), + resource.TestCheckResourceAttrSet(resourceName, "requester_id"), + resource.TestCheckResourceAttrSet(resourceName, "requester_name"), + resource.TestCheckResourceAttrSet(resourceName, "revocation_comment"), + resource.TestCheckResourceAttrSet(resourceName, "revocation_effective_date"), + resource.TestCheckResourceAttrSet(resourceName, "revocation_reason"), + resource.TestCheckResourceAttrSet(resourceName, "serial_number"), + resource.TestCheckResourceAttrSet(resourceName, "signing_algorithm"), + //resource.TestCheckResourceAttrSet(resourceName, "ssl_location"), + //resource.TestCheckResourceAttrSet(resourceName, "subject_alt_name_element"), + resource.TestCheckResourceAttrSet(resourceName, "template_id"), + resource.TestCheckResourceAttrSet(resourceName, "thumbprint"), + resource.TestCheckResourceAttrSet(resourceName, "uri_sans.#"), + ) +} + +func certTestValidateCACert(resourceName string, resourceID string) resource.TestCheckFunc { + return resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "archived_key"), + resource.TestCheckResourceAttr(resourceName, "archived_key", "false"), + resource.TestCheckResourceAttrSet(resourceName, "ca_record_id"), + resource.TestCheckResourceAttrSet(resourceName, "ca_row_index"), + resource.TestCheckResourceAttrSet(resourceName, "cert_state"), + resource.TestCheckResourceAttr(resourceName, "cert_state", APICertStateIsCA), + resource.TestCheckResourceAttrSet(resourceName, "certificate_authority"), + resource.TestCheckResourceAttr(resourceName, "certificate_authority", TestCAName), + resource.TestCheckResourceAttrSet(resourceName, "certificate_authority_id"), + resource.TestCheckNoResourceAttr(resourceName, "certificate_chain"), + resource.TestCheckResourceAttrSet(resourceName, "certificate_id"), + resource.TestCheckResourceAttrSet(resourceName, "certificate_key_id"), + resource.TestCheckResourceAttrSet(resourceName, "certificate_pem"), + resource.TestCheckResourceAttrSet(resourceName, "certificate_template"), + resource.TestCheckResourceAttr(resourceName, "certificate_template", attr.NullValueString), + //resource.TestCheckResourceAttrSet(resourceName, "collection_id"), + resource.TestCheckResourceAttrSet(resourceName, "command_request_id"), + resource.TestCheckResourceAttrSet(resourceName, "content_bytes"), + //resource.TestCheckResourceAttrSet(resourceName, "crl_distribution_point.#"), + resource.TestCheckResourceAttrSet(resourceName, "curve"), + resource.TestCheckResourceAttrSet(resourceName, "detailed_key_usage.%"), + //resource.TestCheckResourceAttrSet(resourceName, "dns_sans.#"), + //resource.TestCheckResourceAttrSet(resourceName, "extended_key_usage.#"), + resource.TestCheckResourceAttrSet(resourceName, "has_private_key"), + resource.TestCheckResourceAttr(resourceName, "has_private_key", "false"), + resource.TestCheckResourceAttrSet(resourceName, "identifier"), + resource.TestCheckResourceAttr(resourceName, "identifier", resourceID), + resource.TestCheckResourceAttrSet(resourceName, "import_date"), + //resource.TestCheckResourceAttrSet(resourceName, "include_private_key"), + //resource.TestCheckResourceAttrSet(resourceName, "ip_sans.#"), + resource.TestCheckResourceAttrSet(resourceName, "issued_cn"), + resource.TestCheckResourceAttrSet(resourceName, "issued_dn"), + resource.TestCheckResourceAttrSet(resourceName, "issued_email"), + resource.TestCheckResourceAttrSet(resourceName, "issued_ou"), + resource.TestCheckResourceAttrSet(resourceName, "issuer_dn"), + // ensure that issued_cn and issued_dn are the same + resource.TestCheckResourceAttrPair(resourceName, "issuer_dn", resourceName, "issued_dn"), + resource.TestCheckResourceAttrSet(resourceName, "key_bits"), + //resource.TestCheckResourceAttrSet(resourceName, "key_password"), + resource.TestCheckResourceAttrSet(resourceName, "key_recoverable"), + resource.TestCheckResourceAttr(resourceName, "key_recoverable", "false"), + resource.TestCheckResourceAttrSet(resourceName, "key_type"), + resource.TestCheckResourceAttrSet(resourceName, "key_usage"), + //resource.TestCheckResourceAttrSet(resourceName, "location"), + //resource.TestCheckResourceAttrSet(resourceName, "locations_count"), + resource.TestCheckResourceAttrSet(resourceName, "metadata.%"), + resource.TestCheckResourceAttrSet(resourceName, "not_after"), + resource.TestCheckResourceAttrSet(resourceName, "not_before"), + resource.TestCheckResourceAttrSet(resourceName, "principal_id"), + resource.TestCheckResourceAttrSet(resourceName, "principal_name"), + resource.TestCheckNoResourceAttr(resourceName, "private_key"), + resource.TestCheckResourceAttrSet(resourceName, "requester_id"), + resource.TestCheckResourceAttrSet(resourceName, "requester_name"), + resource.TestCheckResourceAttrSet(resourceName, "revocation_comment"), + resource.TestCheckResourceAttrSet(resourceName, "revocation_effective_date"), + resource.TestCheckResourceAttrSet(resourceName, "revocation_reason"), + resource.TestCheckResourceAttrSet(resourceName, "serial_number"), + resource.TestCheckResourceAttrSet(resourceName, "signing_algorithm"), + //resource.TestCheckResourceAttrSet(resourceName, "ssl_location"), + //resource.TestCheckResourceAttrSet(resourceName, "subject_alt_name_element"), + resource.TestCheckResourceAttrSet(resourceName, "template_id"), + resource.TestCheckResourceAttrSet(resourceName, "thumbprint"), + //resource.TestCheckResourceAttrSet(resourceName, "uri_sans.#"), + ) +} diff --git a/internal/provider/v3/command/helpers.go b/internal/provider/v3/command/helpers.go new file mode 100644 index 00000000..b0dd2cb5 --- /dev/null +++ b/internal/provider/v3/command/helpers.go @@ -0,0 +1,140 @@ +package command + +import ( + "context" + "encoding/json" + "fmt" + kfc "github.com/Keyfactor/keyfactor-go-client-sdk/v2/api/command" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-log/tflog" + "io" + "net/http" + "sort" + "time" +) + +func logCommandAPIResponse(ctx context.Context, response *http.Response, err error) { + tflog.SetField(ctx, "commandResponseCode", response.StatusCode) + tflog.Debug(ctx, fmt.Sprintf("Command API Response Status: %v", response.Status)) + defer response.Body.Close() + bodyBytes, _ := io.ReadAll(response.Body) + bodyString := string(bodyBytes) + // attempt to convert body string to map[string]string from json + // if successful, log the map[string]string + // if not successful, log the body string + var jsonBody map[string]interface{} + jErr := json.Unmarshal([]byte(bodyString), &jsonBody) + if jErr == nil { + if _, ok := jsonBody["Message"]; ok { + tflog.Debug(ctx, fmt.Sprintf("Command API Response Body: %v", jsonBody["Message"])) + } else { + tflog.Debug(ctx, fmt.Sprintf("Command API Response Body: %v", jsonBody)) + } + } else { + var jsonMultiBody []map[string]interface{} + jErr = json.Unmarshal([]byte(bodyString), &jsonMultiBody) + if response.StatusCode != http.StatusOK && jErr == nil { + tflog.Trace(ctx, fmt.Sprintf("Command API Response Body: %v", jsonMultiBody)) + } else { + tflog.Debug(ctx, fmt.Sprintf("Command API Response Body: %v", bodyString)) + } + } + if err != nil { + tflog.Error(ctx, fmt.Sprintf("Command API Response Error: %v", err)) + } +} + +func convertInt64Ptr(ptr *int32) int64 { + if ptr != nil { + // Dereference the pointer safely + return int64(*ptr) + } else { + return 0 + } +} + +func convertStringPtr(ptr *string) string { + if ptr != nil { + // Dereference the pointer safely + return *ptr + } else { + return attr.NullValueString + } +} + +func convertNullableStringPtr(ptr *kfc.NullableString) string { + if ptr != nil && ptr.IsSet() && ptr.Get() != nil { + // Dereference the pointer safely + return *ptr.Get() + } else { + return types.StringNull().String() + } +} + +func convertNullableIntPtr(ptr *kfc.NullableInt32) int64 { + if ptr != nil && ptr.IsSet() && ptr.Get() != nil { + // Dereference the pointer safely + return int64(*ptr.Get()) + } else { + return types.Int64Null().ValueInt64() + } +} + +func convertNullableTimePtr(ptr *kfc.NullableTime) string { + if ptr != nil && ptr.IsSet() && ptr.Get() != nil { + // Dereference the pointer safely and convert to ISO 8601 string + return ptr.Get().Format(time.RFC3339) + } else { + return types.StringNull().String() + } +} + +func convertTimeToStringPtr(ptr *time.Time) string { + if ptr != nil { + // Dereference the pointer safely and convert to ISO 8601 string + return ptr.Format(time.RFC3339) + } else { + return types.StringNull().String() + } +} + +func convertBoolToPtr(b bool) *bool { + return &b +} + +func convertToTerraformList(list interface{}) (types.List, diag.Diagnostics) { + var tfList basetypes.ListValue + var err diag.Diagnostics + switch list.(type) { + case []int: + // sort input list in place + sort.Ints(list.([]int)) + tfList, err = types.ListValueFrom(context.Background(), types.Int64Type, list) + case []string: + // sort input list in place + sort.Strings(list.([]string)) + tfList, err = types.ListValueFrom(context.Background(), types.StringType, list) + } + if err != nil { + tflog.Error(context.Background(), fmt.Sprintf("Error converting to terraform list: %v", err)) + } + return tfList, err +} + +func convertToTerraformMap(mapInterface interface{}) (types.Map, diag.Diagnostics) { + var tfMap basetypes.MapValue + var err diag.Diagnostics + switch mapInterface.(type) { + case *map[string]string: + tfMap, err = types.MapValueFrom(context.Background(), types.StringType, mapInterface) + case *map[string]int: + tfMap, err = types.MapValueFrom(context.Background(), types.Int64Type, mapInterface) + } + if err != nil { + tflog.Error(context.Background(), fmt.Sprintf("Error converting to terraform map: %v", err)) + } + return tfMap, err +} diff --git a/internal/provider/v3/command/helpers_test.go b/internal/provider/v3/command/helpers_test.go new file mode 100644 index 00000000..8d40cf48 --- /dev/null +++ b/internal/provider/v3/command/helpers_test.go @@ -0,0 +1,8 @@ +package command + +import ( + "regexp" +) + +var emptyCertRequestErrRegex = regexp.MustCompile(`.*Unable to find certificate in Keyfactor Command`) +var invalidAgentRequestErrRegex = regexp.MustCompile(`.*Invalid Agent Identifier*`) diff --git a/internal/provider/v3/command/models.go b/internal/provider/v3/command/models.go new file mode 100644 index 00000000..f253877e --- /dev/null +++ b/internal/provider/v3/command/models.go @@ -0,0 +1,117 @@ +package command + +import ( + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type CertificateDataSourceModel struct { + Id types.Int64 `json:"Id" tfsdk:"certificate_id"` // modified + Thumbprint types.String `json:"Thumbprint" tfsdk:"thumbprint"` + SerialNumber types.String `json:"SerialNumber" tfsdk:"serial_number"` + IssuedDN types.String `json:"IssuedDN" tfsdk:"issued_dn"` + IssuedCN types.String `json:"IssuedCN" tfsdk:"issued_cn"` + ImportDate types.String `json:"ImportDate,omitempty" tfsdk:"import_date"` + NotBefore types.String `json:"NotBefore,omitempty" tfsdk:"not_before"` + NotAfter types.String `json:"NotAfter,omitempty" tfsdk:"not_after"` + IssuerDN types.String `json:"IssuerDN,omitempty" tfsdk:"issuer_dn"` + PrincipalId types.Int64 `json:"PrincipalId,omitempty" tfsdk:"principal_id"` + TemplateId types.Int64 `json:"TemplateId,omitempty" tfsdk:"template_id"` + CertState types.String `json:"CertStateString,omitempty" tfsdk:"cert_state"` + KeySizeInBits types.Int64 `json:"KeySizeInBits" tfsdk:"key_bits"` + KeyType types.String `json:"KeyTypeString" tfsdk:"key_type"` + RequesterId types.Int64 `json:"RequesterId" tfsdk:"requester_id"` + IssuedOU types.String `json:"IssuedOU" tfsdk:"issued_ou"` + IssuedEmail types.String `json:"IssuedEmail" tfsdk:"issued_email"` + KeyUsage types.Int64 `json:"KeyUsage" tfsdk:"key_usage"` + SigningAlgorithm types.String `json:"SigningAlgorithm" tfsdk:"signing_algorithm"` + RevocationEffDate types.String `json:"RevocationEffDate" tfsdk:"revocation_effective_date"` + RevocationReason types.Int64 `json:"RevocationReason" tfsdk:"revocation_reason"` + RevocationComment types.String `json:"RevocationComment" tfsdk:"revocation_comment"` + CertificateAuthorityId types.Int64 `json:"CertificateAuthorityId" tfsdk:"certificate_authority_id"` + CertificateAuthorityName types.String `json:"CertificateAuthorityName" tfsdk:"certificate_authority"` //modified + TemplateName types.String `json:"TemplateName" tfsdk:"certificate_template"` //modified + ArchivedKey types.Bool `json:"ArchivedKey" tfsdk:"archived_key"` + HasPrivateKey types.Bool `json:"HasPrivateKey" tfsdk:"has_private_key"` + PrincipalName types.String `json:"PrincipalName" tfsdk:"principal_name"` + CertRequestId types.Int64 `json:"CertRequestId" tfsdk:"command_request_id"` // modified + RequesterName types.String `json:"RequesterName" tfsdk:"requester_name"` + ContentBytes types.String `json:"ContentBytes" tfsdk:"content_bytes"` + ExtendedKeyUsages []CertificateExtendedKeyUsage `json:"ExtendedKeyUsages" tfsdk:"extended_key_usage"` + SubjectAltNameElements []CertificateSubjectAltName `json:"SubjectAltNameElements" tfsdk:"subject_alt_name_element"` + CRLDistributionPoints []CertificateCRLDistributionPoint `json:"CRLDistributionPoints" tfsdk:"crl_distribution_point"` + LocationsCount []CertificateLocationCount `json:"LocationsCount" tfsdk:"locations_count"` + SSLLocations []CertificateSSLLocation `json:"SSLLocations" tfsdk:"ssl_location"` + Locations []CertificateLocation `json:"Locations" tfsdk:"location"` + Metadata types.Map `json:"Metadata" tfsdk:"metadata"` + CertificateKeyId types.Int64 `json:"CertificateKeyId" tfsdk:"certificate_key_id"` + CARowIndex types.Int64 `json:"CARowIndex" tfsdk:"ca_row_index"` + CARecordId types.String `json:"CARecordId" tfsdk:"ca_record_id"` + DetailedKeyUsage *CertificateDetailedKeyUsage `json:"DetailedKeyUsage" tfsdk:"detailed_key_usage"` + KeyRecoverable types.Bool `json:"KeyRecoverable" tfsdk:"key_recoverable"` + Curve types.String `json:"Curve" tfsdk:"curve"` + // TF specific fields + Identifier types.String `tfsdk:"identifier"` + PEM types.String `tfsdk:"certificate_pem"` + PEMChain types.String `tfsdk:"certificate_chain"` + PrivateKey types.String `tfsdk:"private_key"` + KeyPassword types.String `tfsdk:"key_password"` + DNSSANs types.List `tfsdk:"dns_sans"` + IPSANs types.List `tfsdk:"ip_sans"` + URISANs types.List `tfsdk:"uri_sans"` + IncludePrivateKey types.Bool `tfsdk:"include_private_key"` + CollectionId types.Int64 `tfsdk:"collection_id"` +} + +type CertificateExtendedKeyUsage struct { + Id types.Int64 `json:"Id" tfsdk:"id"` + Oid types.String `json:"Oid" tfsdk:"oid"` + DisplayName types.String `json:"DisplayName" tfsdk:"display_name"` +} + +type CertificateSubjectAltName struct { + Id types.Int64 `json:"Id" tfsdk:"id"` + Value types.String `json:"Value" tfsdk:"value"` + Type types.Int64 `json:"Type" tfsdk:"san_type"` + ValueHash types.String `json:"ValueHash" tfsdk:"value_hash"` +} + +type CertificateCRLDistributionPoint struct { + Id types.Int64 `json:"Id" tfsdk:"id"` + Url types.String `json:"Url" tfsdk:"url"` + UrlHash types.String `json:"UrlHash" tfsdk:"url_hash"` +} + +type CertificateLocationCount struct { + StoreType types.String `json:"Type" tfsdk:"store_type"` + Count types.Int64 `json:"Count" tfsdk:"count"` +} + +type CertificateSSLLocation struct { + StorePath types.String `json:"StorePath" tfsdk:"store_path"` + AgentPool types.String `json:"AgentPool" tfsdk:"agent_pool"` + IPAddress types.String `json:"IPAddress" tfsdk:"ip_address"` + Port types.Int64 `json:"Port" tfsdk:"port"` + NetworkName types.String `json:"NetworkName" tfsdk:"network_name"` +} + +type CertificateLocation struct { + StoreMachineName types.String `json:"StoreMachineName" tfsdk:"store_machine_name"` + StorePath types.String `json:"StorePath" tfsdk:"store_path"` + StoreType types.Int64 `json:"StoreType" tfsdk:"store_type"` + Alias types.String `json:"Alias" tfsdk:"alias"` + ChainLevel types.Int64 `json:"ChainLevel" tfsdk:"chain_level"` + CertStoreId types.String `json:"CertStoreId" tfsdk:"cert_store_id"` +} + +type CertificateDetailedKeyUsage struct { + CrlSign types.Bool `json:"CrlSign" tfsdk:"crl_sign"` + DataEncipherment types.Bool `json:"DataEncipherment" tfsdk:"data_encipherment"` + DecipherOnly types.Bool `json:"DecipherOnly" tfsdk:"decipher_only"` + DigitalSignature types.Bool `json:"DigitalSignature" tfsdk:"digital_signature"` + EncipherOnly types.Bool `json:"EncipherOnly" tfsdk:"encipher_only"` + KeyAgreement types.Bool `json:"KeyAgreement" tfsdk:"key_agreement"` + KeyCertSign types.Bool `json:"KeyCertSign" tfsdk:"key_cert_sign"` + KeyEncipherment types.Bool `json:"KeyEncipherment" tfsdk:"key_encipherment"` + NonRepudiation types.Bool `json:"NonRepudiation" tfsdk:"non_repudiation"` + HexCode types.String `json:"HexCode" tfsdk:"hex_code"` +} diff --git a/internal/provider/v3/command/provider.go b/internal/provider/v3/command/provider.go new file mode 100644 index 00000000..0c78cf49 --- /dev/null +++ b/internal/provider/v3/command/provider.go @@ -0,0 +1,370 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package command + +import ( + "context" + "fmt" + kfc "github.com/Keyfactor/keyfactor-go-client-sdk/v2/api/command" + "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/hashicorp/terraform-plugin-log/tflog" + "net/http" + "os" + "strings" +) + +// Ensure Provider satisfies various provider interfaces. +var _ provider.Provider = &Provider{} + +// Provider defines the provider implementation. +type Provider struct { + // version is set to the provider version on release, "dev" when the + // provider is built and ran locally, and "test" when running acceptance + // testing. + version string +} + +// ProviderModel describes the provider data model. +type ProviderModel struct { + Username types.String `tfsdk:"username"` + Hostname types.String `tfsdk:"hostname"` + Password types.String `tfsdk:"password"` + Domain types.String `tfsdk:"domain"` + ClientID types.String `tfsdk:"client_id"` + ClientSecret types.String `tfsdk:"client_secret"` + AuthConfig types.String `tfsdk:"auth_config"` +} + +func (p *Provider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "keyfactor_command" + resp.Version = p.version +} + +func (p *Provider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "hostname": schema.StringAttribute{ + CustomType: nil, + Required: false, + Optional: true, + Sensitive: true, + Description: "Keyfactor Command hostname including any non-standard port.", + MarkdownDescription: "Keyfactor Command hostname including any non-standard port.", + DeprecationMessage: "", + Validators: nil, + }, + "username": schema.StringAttribute{ + CustomType: nil, + Required: false, + Optional: true, + Sensitive: true, + Description: "The username used to authenticate to Keyfactor Command, if using basic auth. Must be used in conjunction with password.", + MarkdownDescription: "The username used to authenticate to Keyfactor Command, if using basic auth. Must be used in conjunction with password.", + DeprecationMessage: "", + Validators: nil, + }, + "password": schema.StringAttribute{ + CustomType: nil, + Required: false, + Optional: true, + Sensitive: true, + Description: "The password used to authenticate to Keyfactor Command, if using basic auth. Must be used in conjunction with username.", + MarkdownDescription: "The password used to authenticate to Keyfactor Command, if using basic auth. Must be used in conjunction with username.", + DeprecationMessage: "", + Validators: nil, + }, + "domain": schema.StringAttribute{ + CustomType: nil, + Required: false, + Optional: true, + Sensitive: true, + Description: "The active directory domain of the username used to authenticate to Keyfactor Command, if using basic auth. This can be provided in the username field as well by using one of the following patterns: '\\username' or 'username@domain'.", + MarkdownDescription: "The active directory domain of the username used to authenticate to Keyfactor Command, if using basic auth. This can be provided in the username field as well by using one of the following patterns: '\\username' or 'username@domain'.", + DeprecationMessage: "", + Validators: nil, + }, + "client_id": schema.StringAttribute{ + CustomType: nil, + Required: false, + Optional: true, + Sensitive: true, + Description: "The client ID to authenticate to Keyfactor Command, if using oauth. Must be used in conjunction with client_secret.", + MarkdownDescription: "The client ID to authenticate to Keyfactor Command, if using oauth. Must be used in conjunction with client_secret.", + DeprecationMessage: "", + Validators: nil, + }, + "client_secret": schema.StringAttribute{ + CustomType: nil, + Required: false, + Optional: true, + Sensitive: true, + Description: "The client secret to authenticate to Keyfactor Command, if using oauth. Must be used in conjunction with client_id.", + MarkdownDescription: "The client secret to authenticate to Keyfactor Command, if using oauth. Must be used in conjunction with client_id.", + DeprecationMessage: "", + Validators: nil, + }, + "auth_config": schema.StringAttribute{ + CustomType: nil, + Required: false, + Optional: true, + Sensitive: true, + Description: "The path to the auth config file to use for authentication to Keyfactor Command. This can be used in place of the username, password, domain, client_id, and client_secret fields.", + MarkdownDescription: "The path to the auth config file to use for authentication to Keyfactor Command. This can be used in place of the username, password, domain, client_id, and client_secret fields.", + DeprecationMessage: "", + Validators: nil, + }, + }, + } +} + +func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + var data ProviderModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + username, _ := p.resolveUsername(data) + tflog.SetField(ctx, "username", username) + tflog.Debug(ctx, fmt.Sprintf("username: %s", username)) + + password, _ := p.resolvePassword(data) + if password != "" { + tflog.MaskAllFieldValuesStrings(ctx, password) + } + tflog.SetField(ctx, "password", password) + + domain, _ := p.resolveDomain(data) + tflog.SetField(ctx, "domain", domain) + + clientID, _ := p.resolveClientID(data) + tflog.SetField(ctx, "client_id", clientID) + + clientSecret, _ := p.resolveClientSecret(data) + if clientSecret != "" { + tflog.MaskAllFieldValuesStrings(ctx, clientSecret) + } + tflog.SetField(ctx, "client_secret", clientSecret) + + host, _ := p.resolveHost(data) + tflog.SetField(ctx, "host", host) + + tflog.Info(ctx, "Configuring Keyfactor Command provider") + + sdkClientConfig := make(map[string]string) + sdkClientConfig["host"] = host + sdkClientConfig["username"] = username + sdkClientConfig["password"] = password + sdkClientConfig["domain"] = domain + + configErr := p.validateConfig(&sdkClientConfig) + if configErr != nil { + resp.Diagnostics.AddError( + "Command Configuration Error", + fmt.Sprintf("Invalid configuration: %s", configErr.Error()), + ) + return + } + + configuration, cfgErr := kfc.NewConfiguration(sdkClientConfig) + if cfgErr != nil { + resp.Diagnostics.AddError( + "Command Configuration Error", + fmt.Sprintf("Unable to create Keyfactor Command configuration: %s", cfgErr.Error()), + ) + return + } + c := kfc.NewAPIClient(configuration) + + maxRetries := 3 + for i := 0; i <= maxRetries; i++ { + o, r, err := c.StatusApi.StatusGetEndpoints(nil).Execute() + if err != nil { + if i < maxRetries { + tflog.Warn(ctx, fmt.Sprintf("Unable to authenticate to Keyfactor Command: %s", err.Error())) + continue + } + resp.Diagnostics.AddError( + "Command Authentication Error", + fmt.Sprintf("Unable to authenticate to Keyfactor Command: %s", err.Error()), + ) + return + } + if r.StatusCode != http.StatusOK { + if i < maxRetries && (http.StatusRequestTimeout == r.StatusCode || http.StatusServiceUnavailable == r.StatusCode || http.StatusGatewayTimeout == r.StatusCode) { + tflog.Warn(ctx, fmt.Sprintf("Unable to authenticate to Keyfactor Command: %s", o)) + continue + } + resp.Diagnostics.AddError( + "Command Authentication Error", + fmt.Sprintf("Unable to authenticate to Keyfactor Command: %s", o), + ) + return + } else { + tflog.Info(ctx, fmt.Sprintf("Successfully authenticated to Keyfactor Command: %s", o)) + resp.DataSourceData = c + resp.ResourceData = c + return + } + } +} + +func (p *Provider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{} +} + +func (p *Provider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + NewCertificateDataSource, + NewAgentDataSource, + } +} + +func New(version string) func() provider.Provider { + return func() provider.Provider { + return &Provider{ + version: version, + } + } +} + +func (p *Provider) resolveUsername(data ProviderModel) (string, error) { + var username string + if data.Username.IsNull() || data.Username.IsUnknown() { + username = os.Getenv("KEYFACTOR_CMD_USERNAME") + if username == "" { + username = os.Getenv("KEYFACTOR_USERNAME") + } + } else { + username = data.Username.String() + } + return username, nil +} + +func (p *Provider) resolvePassword(data ProviderModel) (string, error) { + var password string + if data.Password.IsNull() || data.Password.IsUnknown() { + password = os.Getenv("KEYFACTOR_CMD_PASSWORD") + if password == "" { + password = os.Getenv("KEYFACTOR_PASSWORD") + } + if password == "" { + password = os.Getenv("KEYFACTOR_CMD_CLIENT_SECRET") + if password == "" { + password = os.Getenv("KEYFACTOR_CLIENT_SECRET") + } + } + } else { + password = data.Password.String() + } + return password, nil +} + +func (p *Provider) resolveDomain(data ProviderModel) (string, error) { + var domain string + if data.Domain.IsNull() || data.Domain.IsUnknown() { + domain = os.Getenv("KEYFACTOR_CMD_DOMAIN") + if domain == "" { + domain = os.Getenv("KEYFACTOR_DOMAIN") + } + } else { + domain = data.Domain.String() + } + if domain == "" { + //try and parse from username + username, _ := p.resolveUsername(data) + if username != "" { + if strings.Contains(username, "\\") { + domain = strings.Split(username, "\\")[0] + } else if strings.Contains(username, "@") { + domain = strings.Split(username, "@")[1] + } + } + } + return domain, nil +} + +func (p *Provider) resolveClientID(data ProviderModel) (string, error) { + var clientID string + if (data.ClientID.IsNull()) || (data.ClientID.IsUnknown()) { + clientID = os.Getenv("KEYFACTOR_CMD_CLIENT_ID") + if clientID == "" { + clientID = os.Getenv("KEYFACTOR_CLIENT_ID") + } + } else { + clientID = data.ClientID.String() + } + return clientID, nil +} + +func (p *Provider) resolveClientSecret(data ProviderModel) (string, error) { + var clientSecret string + if data.Password.IsNull() || data.Password.IsUnknown() { + clientSecret = os.Getenv("KEYFACTOR_CMD_CLIENT_SECRET") + if clientSecret == "" { + clientSecret = os.Getenv("KEYFACTOR_CLIENT_SECRET") + } + } else { + clientSecret = data.ClientSecret.String() + } + return clientSecret, nil +} + +func (p *Provider) resolveHost(data ProviderModel) (string, error) { + var hostname string + if data.Password.IsNull() || data.Password.IsUnknown() { + hostname = os.Getenv("KEYFACTOR_CMD_HOSTNAME") + if hostname == "" { + hostname = os.Getenv("KEYFACTOR_HOSTNAME") + } + } else { + hostname = data.Hostname.String() + } + return hostname, nil +} + +// validateConfig validates the provider configuration. +// This returns an error if the following conditions are not met: +// - host not provided +// - username and client_id not provided +// - username and client_id both provided +// - password not provided when username provided +// - domain not provided when username provided +// - client_secret not provided when client_id provided +func (p *Provider) validateConfig(config *map[string]string) error { + var errs []error + if (*config)["host"] == "" { + errs = append(errs, fmt.Errorf("host not provided")) + } + if ((*config)["username"] == "" && (*config)["client_id"] == "") || ((*config)["username"] != "" && (*config)["client_id"] != "") { + errs = append(errs, fmt.Errorf("username and client_id not provided, or both provided")) + } + if (*config)["username"] != "" { + if (*config)["password"] == "" { + errs = append(errs, fmt.Errorf("password not provided when username provided")) + } + if (*config)["domain"] == "" { + errs = append(errs, fmt.Errorf("domain not provided when username provided")) + } + } + if (*config)["client_id"] != "" { + if (*config)["client_secret"] == "" { + errs = append(errs, fmt.Errorf("client_secret not provided when client_id provided")) + } + } + if len(errs) > 0 { + // combine errors into a single error + var errStr string + for _, err := range errs { + errStr = fmt.Sprintf("%s%s\n", errStr, err.Error()) + } + return fmt.Errorf(errStr) + } + return nil +} diff --git a/internal/provider/v3/command/provider_test.go b/internal/provider/v3/command/provider_test.go new file mode 100644 index 00000000..4d3a5299 --- /dev/null +++ b/internal/provider/v3/command/provider_test.go @@ -0,0 +1,47 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package command + +import ( + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "testing" +) + +const ( + // providerConfig is a shared configuration to combine with the actual + // test configuration so the HashiCups client is properly configured. + // It is also possible to use the HASHICUPS_ environment variables instead, + // such as updating the Makefile and running the testing through that tool. + providerConfig = ` +terraform { + required_version = ">= 1.6" + required_providers { + keyfactor = { + version = "3.0.0" + source = "keyfactor.com/command/v3" + } + } +} +provider "keyfactor" { + auth_config = "~/.keyfactor/command_config.json" +} +` +) + +var ( + // testAccProtoV6ProviderFactories are used to instantiate a provider during + // acceptance testing. The factory function will be invoked for every Terraform + // CLI command executed to create a provider server to which the CLI can + // reattach. + testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){ + "keyfactor": providerserver.NewProtocol6WithError(New("test")()), + } +) + +func testAccPreCheck(t *testing.T) { + // You can add code here to run prior to any test case execution, for example assertions + // about the appropriate environment variables being set are common to see in a pre-check + // function. +} diff --git a/internal/provider/v3/command/version.go b/internal/provider/v3/command/version.go new file mode 100644 index 00000000..7f1ae7eb --- /dev/null +++ b/internal/provider/v3/command/version.go @@ -0,0 +1,3 @@ +package command + +const VERSION = "3.0.0" diff --git a/keyfactor/certificate_helpers.go b/keyfactor/certificate_helpers.go deleted file mode 100644 index b0cd47db..00000000 --- a/keyfactor/certificate_helpers.go +++ /dev/null @@ -1,174 +0,0 @@ -package keyfactor - -import ( - "github.com/Keyfactor/keyfactor-go-client/api" - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/types" - "math/rand" - "strings" -) - -var ( - lowerCharSet = "abcdedfghijklmnopqrst" - upperCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - specialCharSet = "!@#$%&*" - numberSet = "0123456789" - allCharSet = lowerCharSet + upperCharSet + specialCharSet + numberSet -) - -func generatePassword(passwordLength, minSpecialChar, minNum, minUpperCase int) string { - var password strings.Builder - - //Set special character - for i := 0; i < minSpecialChar; i++ { - random := rand.Intn(len(specialCharSet)) - password.WriteString(string(specialCharSet[random])) - } - - //Set numeric - for i := 0; i < minNum; i++ { - random := rand.Intn(len(numberSet)) - password.WriteString(string(numberSet[random])) - } - - //Set uppercase - for i := 0; i < minUpperCase; i++ { - random := rand.Intn(len(upperCharSet)) - password.WriteString(string(upperCharSet[random])) - } - - remainingLength := passwordLength - minSpecialChar - minNum - minUpperCase - for i := 0; i < remainingLength; i++ { - random := rand.Intn(len(allCharSet)) - password.WriteString(string(allCharSet[random])) - } - inRune := []rune(password.String()) - rand.Shuffle(len(inRune), func(i, j int) { - inRune[i], inRune[j] = inRune[j], inRune[i] - }) - return string(inRune) -} - -func flattenSubject(subject string) types.Object { - data := make(map[string]string) // Inner subject interface is a string mapped interface - if subject != "" { - subjectFields := strings.Split(subject, ",") // Separate subject fields into slices - for _, field := range subjectFields { // Iterate and assign slices to associated map - if strings.Contains(field, "CN=") { - //result["subject_common_name"] = types.String{Value: strings.Replace(field, "CN=", "", 1)} - data["subject_common_name"] = strings.Replace(field, "CN=", "", 1) - } else if strings.Contains(field, "OU=") { - //result["subject_organizational_unit"] = types.String{Value: strings.Replace(field, "OU=", "", 1)} - data["subject_organizational_unit"] = strings.Replace(field, "OU=", "", 1) - } else if strings.Contains(field, "C=") { - //result["subject_country"] = types.String{Value: strings.Replace(field, "C=", "", 1)} - data["subject_country"] = strings.Replace(field, "C=", "", 1) - } else if strings.Contains(field, "L=") { - //result["subject_locality"] = types.String{Value: strings.Replace(field, "L=", "", 1)} - data["subject_locality"] = strings.Replace(field, "L=", "", 1) - } else if strings.Contains(field, "ST=") { - //result["subject_state"] = types.String{Value: strings.Replace(field, "ST=", "", 1)} - data["subject_state"] = strings.Replace(field, "ST=", "", 1) - } else if strings.Contains(field, "O=") { - //result["subject_organization"] = types.String{Value: strings.Replace(field, "O=", "", 1)} - data["subject_organization"] = strings.Replace(field, "O=", "", 1) - } - } - - } - result := types.Object{ - Attrs: map[string]attr.Value{ - "subject_common_name": types.String{Value: data["subject_common_name"]}, - "subject_locality": types.String{Value: data["subject_locality"]}, - "subject_organization": types.String{Value: data["subject_organization"]}, - "subject_state": types.String{Value: data["subject_state"]}, - "subject_country": types.String{Value: data["subject_country"]}, - "subject_organizational_unit": types.String{Value: data["subject_organizational_unit"]}, - }, - AttrTypes: map[string]attr.Type{ - "subject_common_name": types.StringType, - "subject_locality": types.StringType, - "subject_organization": types.StringType, - "subject_state": types.StringType, - "subject_country": types.StringType, - "subject_organizational_unit": types.StringType, - }, - } - - return result -} - -func flattenMetadata(metadata interface{}) types.Map { - data := make(map[string]string) - if metadata != nil { - for k, v := range metadata.(map[string]interface{}) { - data[k] = v.(string) - } - } - - result := types.Map{ - Elems: map[string]attr.Value{}, - ElemType: types.StringType, - } - for k, v := range data { - result.Elems[k] = types.String{Value: v} - } - return result -} - -func flattenSANs(sans []api.SubjectAltNameElements) (types.List, types.List, types.List) { - sanIP4Array := types.List{ - ElemType: types.StringType, - Elems: []attr.Value{}, - } - sanDNSArray := types.List{ - ElemType: types.StringType, - Elems: []attr.Value{}, - } - sanURIArray := types.List{ - ElemType: types.StringType, - Elems: []attr.Value{}, - } - if len(sans) > 0 { - for _, san := range sans { - sanName := mapSanIDToName(san.Type) - if sanName == "IP Address" { - sanIP4Array.Elems = append(sanIP4Array.Elems, types.String{Value: san.Value}) - } else if sanName == "DNS Name" { - sanDNSArray.Elems = append(sanDNSArray.Elems, types.String{Value: san.Value}) - } else if sanName == "Uniform Resource Identifier" { - sanURIArray.Elems = append(sanURIArray.Elems, types.String{Value: san.Value}) - } - } - } - - return sanDNSArray, sanIP4Array, sanURIArray -} - -func mapSanIDToName(sanID int) string { - switch sanID { - case 0: - return "Other Name" - case 1: - return "RFC 822 Name" - case 2: - return "DNS Name" - case 3: - return "X400 Address" - case 4: - return "Directory Name" - case 5: - return "Ediparty Name" - case 6: - return "Uniform Resource Identifier" - case 7: - return "IP Address" - case 8: - return "Registered Id" - case 100: - return "MS_NTPrincipalName" - case 101: - return "MS_NTDSReplication" - } - return "" -} diff --git a/keyfactor/data_source_keyfactor_certificate.go b/keyfactor/data_source_keyfactor_certificate.go deleted file mode 100644 index a972049f..00000000 --- a/keyfactor/data_source_keyfactor_certificate.go +++ /dev/null @@ -1,384 +0,0 @@ -package keyfactor - -import ( - "context" - "crypto/ecdsa" - rsa2 "crypto/rsa" - "crypto/x509" - "encoding/base64" - "encoding/pem" - "fmt" - "github.com/Keyfactor/keyfactor-go-client/api" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" -) - -type dataSourceCertificateType struct{} - -func (r dataSourceCertificateType) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfsdk.Schema{ - Attributes: map[string]tfsdk.Attribute{ - "csr": { - Type: types.StringType, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "Base-64 encoded certificate signing request (CSR)", - }, - "key_password": { - Type: types.StringType, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Sensitive: true, - Description: "Optional, used to read the private key if it is password protected.", - }, - "subject": { - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "KeyfactorCertificate subject", - Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ - "subject_common_name": { - Type: types.StringType, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "Subject common name for new certificate", - }, - "subject_locality": { - Type: types.StringType, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "Subject locality for new certificate", - }, - "subject_organization": { - Type: types.StringType, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "Subject organization for new certificate", - }, - "subject_state": { - Type: types.StringType, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "Subject state for new certificate", - }, - "subject_country": { - Type: types.StringType, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "Subject country for new certificate", - }, - "subject_organizational_unit": { - Type: types.StringType, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "Subject organizational unit for new certificate", - }, - }), - }, - "certificate_authority": { - Type: types.StringType, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - //DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // return strings.EqualFold(old, new) - //}, - Description: "Name of certificate authority to deploy certificate with Ex: Example Company CA 1", - }, - "certificate_template": { - Type: types.StringType, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "Short name of certificate template to be deployed", - }, - "dns_sans": { - Type: types.ListType{ElemType: types.StringType}, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "List of DNS names to use as subjects of the certificate", - //DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // // For some reason Terraform detects this particular function as having drift; this function - // // gives us a definitive answer. - // return !d.HasChange(k) - //}, - }, - "uri_sans": { - Type: types.ListType{ElemType: types.StringType}, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "List of URIs to use as subjects of the certificate", - //DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // // For some reason Terraform detects this particular function as having drift; this function - // // gives us a definitive answer. - // return !d.HasChange(k) - //}, - }, - "ip_sans": { - Type: types.ListType{ElemType: types.StringType}, - Computed: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "List of IPs to use as subjects of the certificate", - //DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // // For some reason Terraform detects this particular function as having drift; this function - // // gives us a definitive answer. - // return !d.HasChange(k) - //}, - }, - "metadata": { - Type: types.MapType{ - ElemType: types.StringType, - }, - Optional: true, - Description: "Metadata key-value pairs to be attached to certificate", - }, - "serial_number": { - Type: types.StringType, - Computed: true, - Description: "Serial number of newly enrolled certificate", - }, - "issuer_dn": { - Type: types.StringType, - Computed: true, - Description: "Issuer distinguished name that signed the certificate", - }, - "thumbprint": { - Type: types.StringType, - Computed: true, - Description: "Thumbprint of newly enrolled certificate", - }, - "id": { - Type: types.Int64Type, - Required: true, - Description: "Keyfactor certificate identifier.", - }, - "collection_id": { - Type: types.Int64Type, - Required: false, - Optional: true, - Description: "Optional certificate collection identifier used to ensure user access to the certificate.", - }, - "keyfactor_request_id": { - Type: types.Int64Type, - Computed: true, - Description: "Keyfactor request ID necessary for deploying certificate", - }, - "certificate_pem": { - Type: types.StringType, - Computed: true, - Description: "PEM formatted certificate", - }, - "certificate_chain": { - Type: types.StringType, - Computed: true, - Description: "PEM formatted certificate chain", - }, - "private_key": { - Type: types.StringType, - Computed: true, - Sensitive: true, - Description: "PEM formatted PKCS#1 private key imported if cert_template has KeyRetention set to a value other than None, and the certificate was not enrolled using a CSR.", - }, - }, - }, nil -} - -func (r dataSourceCertificateType) NewDataSource(ctx context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { - return dataSourceCertificate{ - p: *(p.(*provider)), - }, nil -} - -type dataSourceCertificate struct { - p provider -} - -func (r dataSourceCertificate) Read(ctx context.Context, request tfsdk.ReadDataSourceRequest, response *tfsdk.ReadDataSourceResponse) { - var state KeyfactorCertificate - - tflog.Info(ctx, "Reading terraform data resource 'certificate'.") - diags := request.Config.Get(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - certificateID := state.ID.Value - certificateIDInt := int(certificateID) - - collectionID := state.CollectionId.Value - collectionIdInt := int(collectionID) - - tflog.SetField(ctx, "collection_id", collectionID) - tflog.SetField(ctx, "certificate_id", certificateID) - - // Get certificate context - tflog.Info(ctx, fmt.Sprintf("Attempting to lookup certificate '%v' in Keyfactor.", certificateID)) - tflog.Debug(ctx, "Calling Keyfactor GO Client GetCertificateContext") - args := &api.GetCertificateContextArgs{ - IncludeMetadata: boolToPointer(true), - IncludeLocations: boolToPointer(true), - CollectionId: intToPointer(collectionIdInt), - Id: certificateIDInt, - } - cResp, err := r.p.client.GetCertificateContext(args) - if err != nil { - tflog.Error(ctx, "Error calling Keyfactor Go Client GetCertificateContext") - response.Diagnostics.AddError( - "Error reading Keyfactor certificate.", - fmt.Sprintf("Could not retrieve certificate '%s' from Keyfactor: "+err.Error(), certificateID), - ) - return - } - - // Get the password out of current schema - csr := state.CSR.Value - password := state.KeyPassword.Value - - if password == "" { - tflog.Debug(ctx, "Generating password. This will be stored in the state file, but is only used to download and parse the PFX to PEM fields.") - password = generatePassword(32, 1, 1, 1) - state.KeyPassword.Value = password - } - - var ( - leaf string - chain = "" - pKey = "" - dErr = error(nil) - ) - - if cResp.HasPrivateKey { - tflog.Info(ctx, "Requested certificate has a private key attempting to recover from Keyfactor Command.") - pKeyO, _, chainO, dErrO := r.p.client.RecoverCertificate(certificateIDInt, "", "", "", password) - if dErrO != nil { - response.Diagnostics.AddError( - "Error reading Keyfactor certificate.", - fmt.Sprintf("Could not retrieve certificate '%s' from Keyfactor: "+dErrO.Error(), certificateID), - ) - return - } - // Convert string to []byte and then to pem. - //leaf = string(pem.EncodeToMemory(&pem.Block{ - // Type: "CERTIFICATE", - // Bytes: leafO.Raw, - //})) - lBytes, lbErr := base64.StdEncoding.DecodeString(cResp.ContentBytes) - if lbErr != nil { - response.Diagnostics.AddError( - "Error reading Keyfactor certificate.", - fmt.Sprintf("Could not retrieve certificate '%s' from Keyfactor: "+lbErr.Error(), certificateID), - ) - return - } - leaf = string(pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: lBytes, - })) - tflog.Debug(ctx, "Recovered leaf certificate from Keyfactor Command:") - tflog.Debug(ctx, leaf) - tflog.Debug(ctx, "Recovered certificate chain from Keyfactor Command:") - for _, cert := range chainO { - chainLink := string(pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: cert.Raw, - })) - chain = chain + chainLink - tflog.Debug(ctx, chainLink) - } - - tflog.Debug(ctx, "Recovered private key from Keyfactor Command:") - tflog.Debug(ctx, "Attempting RSA private key recovery") - rsa, ok := pKeyO.(*rsa2.PrivateKey) - if ok { - tflog.Debug(ctx, "Recovered RSA private key from Keyfactor Command:") - buf := x509.MarshalPKCS1PrivateKey(rsa) - if len(buf) > 0 { - pKey = string(pem.EncodeToMemory(&pem.Block{ - Bytes: buf, - Type: "RSA PRIVATE KEY", - })) - tflog.Trace(ctx, pKey) - } else { - tflog.Debug(ctx, "Empty Key Recovered from Keyfactor Command.") - } - } else { - tflog.Debug(ctx, "Attempting ECC private key recovery") - ecc, ok := pKeyO.(*ecdsa.PrivateKey) - if ok { - // We don't really care about the error here. An error just means that the key will be blank which isn't a - // reason to fail - tflog.Debug(ctx, "Recovered ECC private key from Keyfactor Command:") - buf, _ := x509.MarshalECPrivateKey(ecc) - if len(buf) > 0 { - pKey = string(pem.EncodeToMemory(&pem.Block{ - Bytes: buf, - Type: "EC PRIVATE KEY", - })) - tflog.Trace(ctx, pKey) - } - } - } - } else { - // Convert string to []byte and then to pem. - tflog.Debug(ctx, "Requested certificate does not have a private key in Keyfactor Command.") - lBytes, lbErr := base64.StdEncoding.DecodeString(cResp.ContentBytes) - if lbErr != nil { - tflog.Error(ctx, "Error decoding certificate content bytes.") - tflog.Error(ctx, lbErr.Error()) - response.Diagnostics.AddError( - "Error reading Keyfactor certificate.", - fmt.Sprintf("Could not retrieve certificate '%s' from Keyfactor: "+lbErr.Error(), certificateID), - ) - return - } - - tflog.Debug(ctx, "Decoding leaf cert.") - leaf = string(pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE", - Bytes: lBytes, - })) - tflog.Debug(ctx, "Recovered leaf certificate from Keyfactor Command:") - tflog.Debug(ctx, leaf) - } - - if dErr != nil { - response.Diagnostics.AddError( - "Error reading Keyfactor certificate.", - fmt.Sprintf("Could not retrieve certificate '%s' from Keyfactor: "+dErr.Error(), certificateID), - ) - } - - subject := flattenSubject(cResp.IssuedDN) - dnsSans, ipSans, uriSans := flattenSANs(cResp.SubjectAltNameElements) - metadata := flattenMetadata(cResp.Metadata) - - var result = KeyfactorCertificate{ - ID: types.Int64{Value: state.ID.Value}, - CSR: types.String{Value: csr}, - Subject: subject, - DNSSANs: dnsSans, - IPSANs: ipSans, - URISANs: uriSans, - SerialNumber: types.String{Value: cResp.SerialNumber}, - IssuerDN: types.String{ - Value: cResp.IssuerDN, - }, - Thumbprint: types.String{Value: cResp.Thumbprint}, - PEM: types.String{Value: leaf}, - PEMChain: types.String{Value: chain}, - PrivateKey: types.String{Value: pKey}, - KeyPassword: types.String{Value: state.KeyPassword.Value}, - CertificateAuthority: types.String{ - Value: cResp.CertificateAuthorityName, - }, - CertificateTemplate: types.String{Value: cResp.TemplateName}, - RequestId: types.Int64{Value: int64(cResp.CertRequestId)}, - Metadata: metadata, - } - - // Set state - diags = response.State.Set(ctx, &result) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } -} diff --git a/keyfactor/data_source_keyfactor_certificate_store.go b/keyfactor/data_source_keyfactor_certificate_store.go deleted file mode 100644 index f33e052f..00000000 --- a/keyfactor/data_source_keyfactor_certificate_store.go +++ /dev/null @@ -1,175 +0,0 @@ -package keyfactor - -import ( - "context" - "fmt" - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" -) - -type dataSourceCertificateStoreType struct{} - -func (r dataSourceCertificateStoreType) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfsdk.Schema{ - Attributes: map[string]tfsdk.Attribute{ - "container_id": { - Type: types.Int64Type, - Computed: true, - Description: "Container identifier of the store's associated certificate store container.", - }, - "client_machine": { - Type: types.StringType, - Computed: true, - Description: "Client machine name; value depends on certificate store type. See API reference guide", - }, - "store_path": { - Type: types.StringType, - Computed: true, - Description: "Path to the new certificate store on a target. Format varies depending on type.", - }, - "store_type": { - Type: types.StringType, - Computed: true, - Description: "Short name of certificate store type. See API reference guide", - }, - "approved": { - Type: types.BoolType, - Optional: true, - //DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // // For some reason Terraform detects this particular function as having drift; this function - // // gives us a definitive answer. - // return !d.HasChange(k) - //}, - Description: "Bool that indicates the approval status of store created. Default is true, omit if unsure.", - }, - "create_if_missing": { - Type: types.BoolType, - Optional: true, - Description: "Bool that indicates if the store should be created with information provided. Valid only for JKS type, omit if unsure.", - }, - "properties": { - Type: types.MapType{ElemType: types.StringType}, - Optional: true, - Description: "Certificate properties specific to certificate store type configured as key-value pairs.", - }, - "agent_id": { - Type: types.StringType, - Computed: true, - Description: "String indicating the Keyfactor Command GUID of the orchestrator for the created store.", - }, - "agent_assigned": { - Type: types.BoolType, - Optional: true, - //DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // // For some reason Terraform detects this particular function as having drift; this function - // // gives us a definitive answer. - // return !d.HasChange(k) - //}, - Description: "Bool indicating if there is an orchestrator assigned to the new certificate store.", - }, - "container_name": { - Type: types.StringType, - Optional: true, - Description: "Name of certificate store's associated container, if applicable.", - }, - "inventory_schedule": { - Type: types.StringType, - Optional: true, - Description: "Inventory schedule for new certificate store.", - }, - "set_new_password_allowed": { - Type: types.BoolType, - Optional: true, - Description: "Indicates whether the store password can be changed.", - }, - "password": { - Type: types.StringType, - Optional: true, - Sensitive: true, - Description: "Sets password for certificate store.", - }, - "id": { - Type: types.StringType, - Required: true, - Description: "Keyfactor certificate store GUID.", - }, - }, - }, nil -} - -func (r dataSourceCertificateStoreType) NewDataSource(ctx context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { - return dataSourceCertificateStore{ - p: *(p.(*provider)), - }, nil -} - -type dataSourceCertificateStore struct { - p provider -} - -func (r dataSourceCertificateStore) Read(ctx context.Context, request tfsdk.ReadDataSourceRequest, response *tfsdk.ReadDataSourceResponse) { - var state CertificateStore - - tflog.Info(ctx, "Read called on certificate resource") - diags := request.Config.Get(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - tflog.Info(ctx, "Read called on certificate store resource") - certificateStoreId := state.ID.Value - - tflog.SetField(ctx, "certificate_id", certificateStoreId) - - sResp, err := r.p.client.GetCertificateStoreByID(certificateStoreId) - if err != nil { - response.Diagnostics.AddError( - "Error reading certificate store", - "Error reading certificate store: %s"+err.Error(), - ) - return - } - - password := state.Password.Value - tflog.Trace(ctx, fmt.Sprintf("Password for store %s: %s", certificateStoreId, password)) - - if err != nil { - response.Diagnostics.AddError( - "Error reading Keyfactor certificate.", - fmt.Sprintf("Could not retrieve certificate '%s' from Keyfactor: "+err.Error(), certificateStoreId), - ) - return - } - - propElems := make(map[string]attr.Value) - for k, v := range sResp.Properties { - propElems[k] = types.String{Value: v} - } - var result = CertificateStore{ - ID: state.ID, - ContainerID: types.Int64{Value: int64(sResp.ContainerId)}, - ContainerName: types.String{Value: sResp.ContainerName}, - AgentId: types.String{Value: sResp.AgentId}, - AgentAssigned: types.Bool{Value: sResp.AgentAssigned}, - ClientMachine: types.String{Value: sResp.ClientMachine}, - StorePath: types.String{Value: sResp.StorePath}, - StoreType: types.String{Value: fmt.Sprintf("%v", sResp.CertStoreType)}, - Approved: types.Bool{Value: sResp.Approved}, - CreateIfMissing: types.Bool{Value: sResp.CreateIfMissing}, - Properties: types.Map{ElemType: types.StringType, Elems: propElems}, - Password: state.Password, - SetNewPasswordAllowed: types.Bool{Value: sResp.SetNewPasswordAllowed}, - InventorySchedule: state.InventorySchedule, - } - - // Set state - diags = response.State.Set(ctx, &result) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } -} diff --git a/keyfactor/data_source_keyfactor_certificate_store_test.go b/keyfactor/data_source_keyfactor_certificate_store_test.go deleted file mode 100644 index b5e0432c..00000000 --- a/keyfactor/data_source_keyfactor_certificate_store_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package keyfactor - -import ( - "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "os" - "testing" -) - -func TestAccKeyfactorCertificateStoreDataSource(t *testing.T) { - var resourceName = fmt.Sprintf("data.%s.test", "keyfactor_certificate_store") - var sID = os.Getenv("KEYFACTOR_CERTIFICATE_STORE_ID") - var sPass = os.Getenv("KEYFACTOR_CERTIFICATE_STORE_PASS") - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - // Read testing - { - Config: testAccDataSourceKeyfactorCertificateStoreBasic(sID, sPass), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "id", sID), - resource.TestCheckResourceAttr(resourceName, "password", sPass), - resource.TestCheckResourceAttrSet(resourceName, "store_path"), - resource.TestCheckResourceAttrSet(resourceName, "store_type"), - resource.TestCheckResourceAttrSet(resourceName, "approved"), - resource.TestCheckResourceAttrSet(resourceName, "create_if_missing"), - resource.TestCheckResourceAttrSet(resourceName, "properties.%"), - resource.TestCheckResourceAttrSet(resourceName, "agent_id"), - resource.TestCheckResourceAttrSet(resourceName, "agent_assigned"), - resource.TestCheckResourceAttrSet(resourceName, "container_name"), - //resource.TestCheckResourceAttrSet(resourceName, "inventory_schedule"), //TODO: Check this when implemented - resource.TestCheckResourceAttrSet(resourceName, "set_new_password_allowed"), - //resource.TestCheckResourceAttrSet(resourceName, "certificates.#"), //TODO: Check this when implemented - ), - }, - }, - }) -} - -func testAccDataSourceKeyfactorCertificateStoreBasic(resourceName string, password string) string { - return fmt.Sprintf(` - data "keyfactor_certificate_store" "test" { - id = "%s" - password = "%s" - } - `, resourceName, password) -} diff --git a/keyfactor/data_source_keyfactor_certificate_test.go b/keyfactor/data_source_keyfactor_certificate_test.go deleted file mode 100644 index 9cb1bc3a..00000000 --- a/keyfactor/data_source_keyfactor_certificate_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package keyfactor - -import ( - "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "os" - "testing" -) - -func TestAccKeyfactorCertificateDataSource(t *testing.T) { - var resourceName = fmt.Sprintf("data.%s.test", "keyfactor_certificate") - var cID = os.Getenv("KEYFACTOR_CERTIFICATE_ID") - var password = os.Getenv("KEYFACTOR_CERTIFICATE_PASSWORD") - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - // Read testing - { - Config: testAccDataSourceKeyfactorCertificateBasic(cID, password), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "id", cID), - resource.TestCheckResourceAttrSet(resourceName, "certificate_authority"), - resource.TestCheckResourceAttrSet(resourceName, "certificate_template"), - resource.TestCheckResourceAttrSet(resourceName, "dns_sans.#"), - resource.TestCheckResourceAttrSet(resourceName, "uri_sans.#"), - resource.TestCheckResourceAttrSet(resourceName, "ip_sans.#"), - resource.TestCheckResourceAttrSet(resourceName, "metadata.%"), - resource.TestCheckResourceAttrSet(resourceName, "serial_number"), - resource.TestCheckResourceAttrSet(resourceName, "subject.%"), - resource.TestCheckResourceAttrSet(resourceName, "issuer_dn"), - resource.TestCheckResourceAttrSet(resourceName, "thumbprint"), - resource.TestCheckResourceAttrSet(resourceName, "keyfactor_request_id"), - resource.TestCheckResourceAttrSet(resourceName, "certificate_pem"), - resource.TestCheckResourceAttrSet(resourceName, "certificate_chain"), - //resource.TestCheckResourceAttrSet(resourceName, "private_key"), - ), - }, - }, - }) -} - -func testAccDataSourceKeyfactorCertificateBasic(resourceName string, password string) string { - return fmt.Sprintf(` - data "keyfactor_certificate" "test" { - id = %s - key_password = "%s" - } - `, resourceName, password) -} diff --git a/keyfactor/data_source_keyfactor_security_identity.go b/keyfactor/data_source_keyfactor_security_identity.go deleted file mode 100644 index 28710fde..00000000 --- a/keyfactor/data_source_keyfactor_security_identity.go +++ /dev/null @@ -1,122 +0,0 @@ -package keyfactor - -import ( - "context" - "fmt" - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" -) - -type dataSourceSecurityIdentityType struct{} - -func (r dataSourceSecurityIdentityType) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfsdk.Schema{ - Attributes: map[string]tfsdk.Attribute{ - "account_name": { - Type: types.StringType, - Required: true, - Description: "A string containing the account name for the security identity. For Active Directory user and groups, this will be in the form DOMAIN\\\\user or group name", - PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - }, - }, - "roles": { - Type: types.ListType{ - ElemType: types.StringType, - }, - Optional: true, - Description: "An array containing the role IDs that the identity is attached to.", - }, - "id": { - Type: types.Int64Type, - Computed: true, - Description: "An integer containing the Keyfactor Command identifier for the security identity.", - }, - "identity_type": { - Type: types.StringType, - Computed: true, - Description: "A string indicating the type of identity—User or Group.", - }, - "valid": { - Type: types.BoolType, - Computed: true, - Description: "A Boolean that indicates whether the security identity's audit XML is valid (true) or not (false). A security identity may become invalid if Keyfactor Command determines that it appears to have been tampered with.", - }, - }, - }, nil -} - -func (r dataSourceSecurityIdentityType) NewDataSource(ctx context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { - return dataSourceSecurityIdentity{ - p: *(p.(*provider)), - }, nil -} - -type dataSourceSecurityIdentity struct { - p provider -} - -func (r dataSourceSecurityIdentity) Read(ctx context.Context, request tfsdk.ReadDataSourceRequest, response *tfsdk.ReadDataSourceResponse) { - var state SecurityIdentity - diags := request.Config.Get(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - tflog.Info(ctx, "Read called on security identity resource") - identityId := state.ID.Value - accountName := state.AccountName.Value - tflog.SetField(ctx, "id", identityId) - - identities, err := r.p.client.GetSecurityIdentities() - - if err != nil { - response.Diagnostics.AddError("Error listing identities from Keyfactor.", "Error reading identities: "+err.Error()) - } - - for _, identity := range identities { - if accountName == identity.AccountName { - tflog.Info(ctx, fmt.Sprintf("Found identity with account name: %s", accountName)) - - var validRoles []attr.Value - var validRolesInterface []interface{} - for _, role := range identity.Roles { - //validRoles = append(validRoles.Elems, role.Name.Value) - tflog.Info(ctx, fmt.Sprintf("Adding role: %s", role.Name)) - tflog.Debug(ctx, fmt.Sprintf("Looking up role %s in Keyfactor", role.Name)) - - kfRole, roleLookupErr := r.p.client.GetSecurityRole(role.Name) - if roleLookupErr != nil || kfRole == nil { - tflog.Warn(ctx, fmt.Sprintf("Error looking up role %v on Keyfactor.", role)) - response.Diagnostics.AddWarning( - "Error looking up role on Keyfactor.", - fmt.Sprintf("Error looking up role '%s' on Keyfactor. '%s' will not have role '%s'.", role.Name, state.AccountName.Value, role.Name), - ) - continue - } - validRoles = append(validRoles, types.String{Value: fmt.Sprintf("%s", role.Name)}) - validRolesInterface = append(validRolesInterface, kfRole.Id) - } - - state = SecurityIdentity{ - ID: types.Int64{Value: int64(identity.Id)}, - AccountName: types.String{Value: identity.AccountName}, - IdentityType: types.String{Value: identity.IdentityType}, - Roles: types.List{Elems: validRoles, ElemType: types.StringType}, - Valid: types.Bool{Value: identity.Valid}, - } - break - } - - } - - diags = response.State.Set(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } -} diff --git a/keyfactor/data_source_keyfactor_security_identity_test.go b/keyfactor/data_source_keyfactor_security_identity_test.go deleted file mode 100644 index 5a8435fd..00000000 --- a/keyfactor/data_source_keyfactor_security_identity_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package keyfactor - -import ( - "fmt" - "os" - "strings" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -func TestAccKeyfactorSecurityIdentityDataSource(t *testing.T) { - var resourceName = fmt.Sprintf("data.%s.test", "keyfactor_identity") - var iNameEscaped = fmt.Sprintf("%s\\\\%s", strings.ToUpper(os.Getenv("KEYFACTOR_DOMAIN")), os.Getenv("KEYFACTOR_USERNAME")) - var iName = fmt.Sprintf("%s\\%s", strings.ToUpper(os.Getenv("KEYFACTOR_DOMAIN")), os.Getenv("KEYFACTOR_USERNAME")) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - // Read testing - { - Config: testAccKeyfactorDataSourceSecurityIdentityBasic(iNameEscaped), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(resourceName, "id"), - resource.TestCheckResourceAttr(resourceName, "account_name", iName), - resource.TestCheckResourceAttrSet(resourceName, "roles.#"), - resource.TestCheckResourceAttrSet(resourceName, "identity_type"), - resource.TestCheckResourceAttrSet(resourceName, "valid"), - ), - }, - }, - }) -} - -func testAccKeyfactorDataSourceSecurityIdentityBasic(identityName string) string { - return fmt.Sprintf(` - data "keyfactor_identity" "test" { - account_name = "%s" - } - `, identityName) -} diff --git a/keyfactor/data_source_keyfactor_security_role.go b/keyfactor/data_source_keyfactor_security_role.go deleted file mode 100644 index d441700b..00000000 --- a/keyfactor/data_source_keyfactor_security_role.go +++ /dev/null @@ -1,98 +0,0 @@ -package keyfactor - -import ( - "context" - "fmt" - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" -) - -type dataSourceSecurityRoleType struct{} - -func (r dataSourceSecurityRoleType) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfsdk.Schema{ - Attributes: map[string]tfsdk.Attribute{ - "id": { - Type: types.Int64Type, - Computed: true, - Description: "Internal ID of the role.", - }, - "name": { - Type: types.StringType, - Required: true, - Description: "An string associated with a Keyfactor security role.", - }, - "description": { - Type: types.StringType, - Computed: true, - Description: "A string containing the description of the role in Keyfactor", - }, - "permissions": { - Type: types.ListType{ElemType: types.StringType}, - Computed: true, - Description: "An array containing the permissions assigned to the role in a list of Name:Value pairs", - }, - }, - }, nil -} - -func (r dataSourceSecurityRoleType) NewDataSource(ctx context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { - return dataSourceSecurityRole{ - p: *(p.(*provider)), - }, nil -} - -type dataSourceSecurityRole struct { - p provider -} - -func (r dataSourceSecurityRole) Read(ctx context.Context, request tfsdk.ReadDataSourceRequest, response *tfsdk.ReadDataSourceResponse) { - tflog.Info(ctx, "Read called on security remoteState resource") - var state SecurityRole - - tflog.Info(ctx, "Read called on security role.") - diags := request.Config.Get(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - roleId := state.Name.Value - tflog.SetField(ctx, "role_id", roleId) - - remoteState, err := r.p.client.GetSecurityRole(roleId) - if remoteState == nil { - response.Diagnostics.AddError("Unknown role error.", fmt.Sprintf("Unable to find role '%v' on Keyfactor. Read failed. ", roleId)) - return - } - - if err != nil { - response.Diagnostics.AddError("Unknown role error.", fmt.Sprintf("Unknown error while trying to import role '%v' on Keyfactor. Read failed. "+err.Error(), roleId)) - return - } - - var permissionValues []attr.Value - for _, perm := range remoteState.Permissions { - tflog.Debug(ctx, fmt.Sprintf("Permission: %v", perm)) - permissionValues = append(permissionValues, types.String{Value: perm}) - } - - var result = SecurityRole{ - ID: types.Int64{Value: int64(remoteState.Id)}, - Name: types.String{Value: remoteState.Name}, - Description: types.String{Value: remoteState.Description}, - Permissions: types.List{ElemType: types.StringType, Elems: permissionValues}, - } - - diags = response.State.Set(ctx, result) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - if response.Diagnostics.HasError() { - return - } -} diff --git a/keyfactor/data_source_keyfactor_security_role_test.go b/keyfactor/data_source_keyfactor_security_role_test.go deleted file mode 100644 index 76f68c10..00000000 --- a/keyfactor/data_source_keyfactor_security_role_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package keyfactor - -import ( - "fmt" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -func TestAccKeyfactorSecurityRoleDataSource(t *testing.T) { - var resourceName = fmt.Sprintf("data.%s.test", "keyfactor_role") - var rName = "Administrator" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - // Read testing - { - Config: testAccDataSourceKeyfactorSecurityRoleBasic(rName), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "id", "1"), - resource.TestCheckResourceAttr(resourceName, "name", rName), - resource.TestCheckResourceAttrSet(resourceName, "description"), - resource.TestCheckResourceAttrSet(resourceName, "permissions.#"), - ), - }, - }, - }) -} - -func testAccDataSourceKeyfactorSecurityRoleBasic(resourceName string) string { - return fmt.Sprintf(` - data "keyfactor_role" "test" { - name = "%s" - } - `, resourceName) -} diff --git a/keyfactor/data_source_keyfactor_template.go b/keyfactor/data_source_keyfactor_template.go deleted file mode 100644 index dfd6886d..00000000 --- a/keyfactor/data_source_keyfactor_template.go +++ /dev/null @@ -1,248 +0,0 @@ -package keyfactor - -import ( - "context" - "fmt" - "github.com/Keyfactor/keyfactor-go-client/api" - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" - "strconv" -) - -type dataSourceCertificateTemplateType struct{} - -func (r dataSourceCertificateTemplateType) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfsdk.Schema{ - Attributes: map[string]tfsdk.Attribute{ - "id": { - Type: types.Int64Type, - Computed: true, - Description: "An integer indicating the ID of the template in Keyfactor Command.", - }, - "short_name": { - Type: types.StringType, - Required: true, - Description: "A string containing the common name (short name) of the template. This name typically does not contain spaces. For a template created using a Microsoft management tool, this will be the Microsoft template name. This field is populated from Active Directory and is not configurable.", - }, - "name": { - Type: types.StringType, - Computed: true, - Description: "A string containing the name of the template. For a template created using a Microsoft management tool, this will be the Microsoft template display name. This field is populated from Active Directory and is not configurable.", - }, - "oid": { - Type: types.StringType, - Computed: true, - Description: "A string containing the object ID of the template in Active Directory. This field is populated from Active Directory and is not configurable.", - }, - "key_size": { - Type: types.StringType, - Computed: true, - Description: "A string indicating the minimum supported key size of the template. This field is populated from Active Directory and is not configurable.", - }, - "key_type": { - Type: types.StringType, - Computed: true, - Description: "A string indicating the key type of the template. This field is populated from Active Directory and is not configurable.", - }, - "forest_root": { - Type: types.StringType, - Computed: true, - Description: "Forest root that the template is stored under/created by", - }, - "friendly_name": { - Type: types.StringType, - Computed: true, - Description: "Forest root that the template is stored under/created by", - }, - "key_retention": { - Type: types.StringType, - Computed: true, - Description: "A string indicating the type of key retention certificates enrolled with this template will use to store their private key in Keyfactor Command. ClosedShow key retention details.", - }, - "key_retention_days": { - Type: types.Int64Type, - Computed: true, - Description: "Duration that the private key should be retained", - }, - "key_archival": { - Type: types.BoolType, - Computed: true, - Description: "A Boolean indicating whether the template has been configured with the key archival setting in Active Directory (true) or not (false). This is a reference field and is not configurable.", - }, - "enrollment_fields": { - Type: types.ListType{ElemType: types.MapType{ElemType: types.StringType}}, - Computed: true, - Description: "An array containing custom enrollment fields. These are configured on a per-template basis to allow you to submit custom fields with CSR enrollments and PFX enrollments to supply custom request attributes to the CA during the enrollment process.", - }, - "allowed_enrollment_types": { - Type: types.Int64Type, - Computed: true, - Description: "An integer indicating the type of enrollment allowed for the certificate template. Setting these options causes the template to appear in dropdowns in the corresponding section of the Management Portal. In the case of CSR Enrollment and PFX Enrollment, the templates only appear in dropdowns on the enrollment pages if they are available for enrollment from a CA also configured for enrollment within Keyfactor Command.", - }, - "template_regexes": { - Type: types.ListType{ElemType: types.StringType}, - Computed: true, - Description: "List of regexes that the template will be matched against during enrollment.", - }, - //"use_allowed_requesters": { - // Type: types.BoolType, - // Computed: true, - // Description: "A Boolean that indicates whether the Restrict Allowed Requesters option should be enabled (true) or not (false). The Restrict Allowed Requesters option is used to select Keyfactor Command security templates that a user must belong to in order to successfully enroll for certificates in Keyfactor Command using this template. This is typically used for templates for untrusted CAs, since Keyfactor Command cannot make use of the access control model of the CA itself to determine which users can enroll for certificates at either a template or CA level; this setting replaces that functionality.", - //}, - - "allowed_requesters": { - Type: types.ListType{ElemType: types.StringType}, - Description: "An array containing the list of Keyfactor Command security templates—as strings—that have been granted enroll permission on the template.", - Optional: true, - Computed: true, - }, - "rfc_enforcement": { - Type: types.BoolType, - Computed: true, - Description: "A Boolean indicating whether certificate enrollments made through Keyfactor Command for this template must include at least one DNS SAN (true) or not (false). In the Keyfactor Command Management Portal, this causes the CN entered in PFX enrollment to automatically be replicated as a SAN, which the user can either change or accept.", - }, - "requires_approval": { - Type: types.BoolType, - Computed: true, - Description: "A Boolean indicating whether certificate enrollments require approval (true) or not (false).", - }, - "key_usage": { - Type: types.Int64Type, - Computed: true, - Description: "An integer indicating the total key usage of the certificate. Key usage is stored in Active Directory as a single value made of a combination of values.", - }, - //"extended_key_usage": { - // Type: types.ListType{ElemType: types.MapType{}}, - // Computed: true, - // Description: "An array containing custom enrollment fields. These are configured on a per-template basis to allow you to submit custom fields with CSR enrollments and PFX enrollments to supply custom request attributes to the CA during the enrollment process.", - //}, - }, - }, nil -} - -func (r dataSourceCertificateTemplateType) NewDataSource(ctx context.Context, p tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { - return dataSourceCertificateTemplate{ - p: *(p.(*provider)), - }, nil -} - -type dataSourceCertificateTemplate struct { - p provider -} - -func (r dataSourceCertificateTemplate) Read(ctx context.Context, request tfsdk.ReadDataSourceRequest, response *tfsdk.ReadDataSourceResponse) { - var state CertificateTemplate - diags := request.Config.Get(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - tflog.Info(ctx, "Read called on certificate template resource") - templateId := state.ID.Value - templateName := state.CommonName.Value - tflog.SetField(ctx, "template_name", templateId) - - templates, err := r.p.client.GetTemplates() - - if err != nil { - response.Diagnostics.AddError("Error listing templates from Keyfactor.", "Error reading templates: "+err.Error()) - } - - var result CertificateTemplate - for _, template := range templates { - if templateName == template.CommonName { - allowedRequesters := flattenAllowedRequesters(template.AllowedRequesters) - templateRegexes := flattenTemplateRegexes(template.TemplateRegexes) - enrollmentFields := flattenEnrollmentFields(template.EnrollmentFields) //TODO: Fix this it causes a panic... - tflog.Debug(ctx, fmt.Sprintf("Enrollment fields: %v", enrollmentFields)) - tflog.Info(ctx, fmt.Sprintf("Found template with account name: %s", templateName)) - result = CertificateTemplate{ - ID: types.Int64{Value: int64(template.Id)}, - CommonName: types.String{Value: template.CommonName}, - TemplateName: types.String{Value: template.TemplateName}, - OID: types.String{Value: template.Oid}, - KeySize: types.String{Value: template.KeySize}, - ForestRoot: types.String{Value: template.ForestRoot}, - FriendlyName: types.String{Value: template.FriendlyName}, - KeyRetention: types.String{Value: template.KeyRetention}, - KeyRetentionDays: types.Int64{Value: int64(template.KeyRetentionDays)}, - KeyArchival: types.Bool{Value: template.KeyArchival}, - KeyType: types.String{Value: template.KeyType}, - EnrollmentFields: state.EnrollmentFields, - AllowedEnrollmentTypes: types.Int64{Value: int64(template.AllowedEnrollmentTypes)}, - TemplateRegexes: templateRegexes, - AllowedRequesters: allowedRequesters, - RFCEnforcement: types.Bool{Value: template.RFCEnforcement}, - RequiresApproval: types.Bool{Value: template.RequiresApproval}, - KeyUsage: types.Int64{Value: int64(template.KeyUsage)}, - } - break - } - } - - diags = response.State.Set(ctx, &result) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } -} - -func flattenEnrollmentFields(efs []api.TemplateEnrollmentFields) types.List { - - result := types.List{ - ElemType: types.MapType{}, - Elems: []attr.Value{}, - } - for _, ef := range efs { - var options []attr.Value - for _, op := range ef.Options { - options = append(options, types.String{ - Value: op, - }) - } - result.Elems = append(result.Elems, types.Map{ - ElemType: types.StringType, - Elems: map[string]attr.Value{ - "id": types.Int64{Value: int64(ef.Id)}, - "name": types.String{Value: ef.Name}, - "type": types.String{Value: strconv.Itoa(ef.DataType)}, - "options": types.List{ - Elems: options, - ElemType: types.StringType, - }, - }, - }) - } - - return result -} - -func flattenTemplateRegexes(regexes []api.TemplateRegex) types.List { - result := types.List{ - ElemType: types.StringType, - Elems: []attr.Value{}, - } - for _, regex := range regexes { - result.Elems = append(result.Elems, types.String{Value: regex.RegEx}) - } - return result -} - -func flattenAllowedRequesters(requesters []string) types.List { - result := types.List{ - ElemType: types.StringType, - Elems: []attr.Value{}, - } - - if len(requesters) > 0 { - for _, requester := range requesters { - result.Elems = append(result.Elems, types.String{Value: requester}) - } - } - - return result -} diff --git a/keyfactor/data_source_keyfactor_template_test.go b/keyfactor/data_source_keyfactor_template_test.go deleted file mode 100644 index b9ebd6f3..00000000 --- a/keyfactor/data_source_keyfactor_template_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package keyfactor - -import ( - "fmt" - "os" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -func TestAccKeyfactorCertificateTemplateDataSource(t *testing.T) { - var resourceName = fmt.Sprintf("data.%s.test", "keyfactor_certificate_template") - var shortName = os.Getenv("KEYFACTOR_TEMPLATE_ROLE_BINDING_TEMPLATE_NAME1") - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - // Read testing - { - Config: testAccDataSourceKeyfactorCertificateTemplateBasic(shortName), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "short_name", shortName), - resource.TestCheckResourceAttrSet(resourceName, "id"), - resource.TestCheckResourceAttrSet(resourceName, "name"), - resource.TestCheckResourceAttrSet(resourceName, "oid"), - resource.TestCheckResourceAttrSet(resourceName, "key_size"), - resource.TestCheckResourceAttrSet(resourceName, "key_type"), - resource.TestCheckResourceAttrSet(resourceName, "forest_root"), - resource.TestCheckResourceAttrSet(resourceName, "friendly_name"), //TODO: This is causing issues - resource.TestCheckResourceAttrSet(resourceName, "key_retention"), - resource.TestCheckResourceAttrSet(resourceName, "key_retention_days"), - resource.TestCheckResourceAttrSet(resourceName, "key_archival"), - //resource.TestCheckResourceAttrSet(resourceName, "enrollment_fields.#"), // TODO: Check this - resource.TestCheckResourceAttrSet(resourceName, "allowed_enrollment_types"), - resource.TestCheckResourceAttrSet(resourceName, "template_regexes.#"), - resource.TestCheckResourceAttrSet(resourceName, "allowed_requesters.#"), - resource.TestCheckResourceAttrSet(resourceName, "rfc_enforcement"), - resource.TestCheckResourceAttrSet(resourceName, "requires_approval"), - resource.TestCheckResourceAttrSet(resourceName, "key_usage"), - ), - }, - }, - }) -} - -func testAccDataSourceKeyfactorCertificateTemplateBasic(resourceName string) string { - return fmt.Sprintf(` - data "keyfactor_certificate_template" "test" { - short_name = "%s" - } - `, resourceName) -} diff --git a/keyfactor/models.go b/keyfactor/models.go deleted file mode 100644 index 34b8eb28..00000000 --- a/keyfactor/models.go +++ /dev/null @@ -1,133 +0,0 @@ -package keyfactor - -import ( - "github.com/hashicorp/terraform-plugin-framework/types" -) - -// Security Identity - -type SecurityIdentity struct { - ID types.Int64 `tfsdk:"id"` - AccountName types.String `tfsdk:"account_name"` - Roles types.List `tfsdk:"roles"` - IdentityType types.String `tfsdk:"identity_type"` - Valid types.Bool `tfsdk:"valid"` -} - -// Security Role - -type SecurityRole struct { - ID types.Int64 `tfsdk:"id"` - Name types.String `tfsdk:"name"` - Description types.String `tfsdk:"description"` - Permissions types.List `tfsdk:"permissions"` -} - -type KeyfactorCertificate struct { - ID types.Int64 `tfsdk:"id"` - // CSR Request Fields - CSR types.String `tfsdk:"csr"` - // PFX KfCertificate Fields - Subject types.Object `tfsdk:"subject"` - DNSSANs types.List `tfsdk:"dns_sans"` - IPSANs types.List `tfsdk:"ip_sans"` - URISANs types.List `tfsdk:"uri_sans"` - SerialNumber types.String `tfsdk:"serial_number"` - IssuerDN types.String `tfsdk:"issuer_dn"` - Thumbprint types.String `tfsdk:"thumbprint"` - PEM types.String `tfsdk:"certificate_pem"` - PEMChain types.String `tfsdk:"certificate_chain"` - PrivateKey types.String `tfsdk:"private_key"` - KeyPassword types.String `tfsdk:"key_password"` - // Keyfactor Fields - CertificateAuthority types.String `tfsdk:"certificate_authority"` - CertificateTemplate types.String `tfsdk:"certificate_template"` - RequestId types.Int64 `tfsdk:"keyfactor_request_id"` - Metadata types.Map `tfsdk:"metadata"` - CollectionId types.Int64 `tfsdk:"collection_id"` -} - -type KeyfactorCertificateDeployment struct { - ID types.String `tfsdk:"id"` - CertificateId types.Int64 `tfsdk:"certificate_id"` - CertificateAlias types.String `tfsdk:"certificate_alias"` - StoreId types.String `tfsdk:"certificate_store_id"` - KeyPassword types.String `tfsdk:"key_password"` -} - -type CSRCertificate struct { - ID types.Int64 `tfsdk:"keyfactor_id"` - // CSR Request Fields - CSR types.String `tfsdk:"csr"` - // PFX KfCertificate Fields - DNSSANs types.List `tfsdk:"dns_sans"` - IPSANs types.List `tfsdk:"ip_sans"` - URISANs types.List `tfsdk:"uri_sans"` - SerialNumber types.String `tfsdk:"serial_number"` - IssuerDN types.String `tfsdk:"issuer_dn"` - Thumbprint types.String `tfsdk:"thumbprint"` - PEM types.String `tfsdk:"certificate_pem"` - PEMChain types.String `tfsdk:"certificate_chain"` - // Keyfactor Fields - CertificateAuthority types.String `tfsdk:"certificate_authority"` - CertificateTemplate types.String `tfsdk:"certificate_template"` - RequestId types.Int64 `tfsdk:"keyfactor_request_id"` - Metadata types.Map `tfsdk:"metadata"` -} - -type CertificateRequest struct { - Certificate KeyfactorCertificate `tfsdk:"certificate"` - CN types.String `tfsdk:"subject_common_name"` - L types.String `tfsdk:"subject_locality"` - O types.String `tfsdk:"subject_organization"` - OU types.String `tfsdk:"subject_organizational_unit"` - ST types.String `tfsdk:"subject_state"` - C types.String `tfsdk:"subject_country"` - Email types.String `tfsdk:"subject_email"` - DNSSANs types.List `tfsdk:"dns_subject_alternative_names"` - IPSANs types.List `tfsdk:"ip_subject_alternative_names"` - URISANs types.List `tfsdk:"uri_subject_alternative_names"` -} - -type CertificateStore struct { - ID types.String `tfsdk:"id"` - ContainerID types.Int64 `tfsdk:"container_id"` - ContainerName types.String `tfsdk:"container_name"` - AgentId types.String `tfsdk:"agent_id"` - AgentAssigned types.Bool `tfsdk:"agent_assigned"` - ClientMachine types.String `tfsdk:"client_machine"` - StorePath types.String `tfsdk:"store_path"` - StoreType types.String `tfsdk:"store_type"` - Approved types.Bool `tfsdk:"approved"` - CreateIfMissing types.Bool `tfsdk:"create_if_missing"` - Properties types.Map `tfsdk:"properties"` - Password types.String `tfsdk:"password"` - SetNewPasswordAllowed types.Bool `tfsdk:"set_new_password_allowed"` - InventorySchedule types.String `tfsdk:"inventory_schedule"` -} - -type CertificateTemplate struct { - ID types.Int64 `tfsdk:"id"` - CommonName types.String `tfsdk:"short_name"` - TemplateName types.String `tfsdk:"name"` - OID types.String `tfsdk:"oid"` - KeySize types.String `tfsdk:"key_size"` - KeyType types.String `tfsdk:"key_type"` - ForestRoot types.String `tfsdk:"forest_root"` - FriendlyName types.String `tfsdk:"friendly_name"` - KeyRetention types.String `tfsdk:"key_retention"` - KeyRetentionDays types.Int64 `tfsdk:"key_retention_days"` - KeyArchival types.Bool `tfsdk:"key_archival"` - EnrollmentFields types.List `tfsdk:"enrollment_fields"` - AllowedEnrollmentTypes types.Int64 `tfsdk:"allowed_enrollment_types"` - TemplateRegexes types.List `tfsdk:"template_regexes"` - AllowedRequesters types.List `tfsdk:"allowed_requesters"` - RFCEnforcement types.Bool `tfsdk:"rfc_enforcement"` - RequiresApproval types.Bool `tfsdk:"requires_approval"` - KeyUsage types.Int64 `tfsdk:"key_usage"` - //ExtendedKeyUsage types.List `tfsdk:"extended_key_usage"` -} - -type CertificateTemplateRoleBinding struct { - ID types.String `tfsdk:"id"` - RoleName types.String `tfsdk:"role_name"` - TemplateNames types.List `tfsdk:"template_short_names"` -} diff --git a/keyfactor/provider.go b/keyfactor/provider.go deleted file mode 100644 index 37bb20c5..00000000 --- a/keyfactor/provider.go +++ /dev/null @@ -1,264 +0,0 @@ -package keyfactor - -import ( - "context" - "github.com/Keyfactor/keyfactor-go-client/api" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - "os" -) - -var stderr = os.Stderr - -func New() tfsdk.Provider { - return &provider{} -} - -type provider struct { - configured bool - client *api.Client -} - -// GetSchema -func (p *provider) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfsdk.Schema{ - Attributes: map[string]tfsdk.Attribute{ - "hostname": { - Type: types.StringType, - Optional: true, - Description: "Hostname of Keyfactor instance. Ex: keyfactor.examplecompany.com. This can also be set via the `KEYFACTOR_HOSTNAME` environment variable.", - }, - - "username": { - Type: types.StringType, - Optional: true, - Description: "Username of Keyfactor service account. This can also be set via the `KEYFACTOR_USERNAME` environment variable.", - }, - - "password": { - Type: types.StringType, - Optional: true, - Sensitive: true, - Description: "Password of Keyfactor service account. This can also be set via the `KEYFACTOR_PASSWORD` environment variable.", - }, - - "appkey": { - Type: types.StringType, - Optional: true, - Sensitive: true, - Description: "Application key provisioned by Keyfactor instance. This can also be set via the `KEYFACTOR_APPKEY` environment variable.", - }, - - "domain": { - Type: types.StringType, - Optional: true, - Description: "Domain that Keyfactor instance is hosted on. This can also be set via the `KEYFACTOR_DOMAIN` environment variable.", - }, - }, - }, nil -} - -// Provider schema struct -type providerData struct { - Username types.String `tfsdk:"username"` - Hostname types.String `tfsdk:"hostname"` - Password types.String `tfsdk:"password"` - ApiKey types.String `tfsdk:"appkey"` - Domain types.String `tfsdk:"domain"` -} - -func (p *provider) Configure(ctx context.Context, req tfsdk.ConfigureProviderRequest, resp *tfsdk.ConfigureProviderResponse) { - // Retrieve provider data from configuration - var config providerData - - diags := req.Config.Get(ctx, &config) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - // User must provide a user to the provider - var username string - if config.Username.Unknown { - // Cannot connect to client with an unknown value - resp.Diagnostics.AddWarning( - "Invalid provider username.", - "Cannot use unknown value as `username`", - ) - return - } - if config.Username.Null { - username = os.Getenv("KEYFACTOR_USERNAME") - config.Username.Value = username - } else { - username = config.Username.Value - } - if username == "" { - // Error vs warning - empty value must stop execution - resp.Diagnostics.AddError( - "Invalid provider username.", - "`username` cannot be an empty string.", - ) - return - } - // User must provide a user to the provider - var domain string - if config.Domain.Unknown { - // Cannot connect to client with an unknown value - resp.Diagnostics.AddWarning( - "Invalid provider `domain`.", - "Cannot use unknown value for `domain`.", - ) - return - } - if config.Domain.Null { - domain = os.Getenv("KEYFACTOR_DOMAIN") - config.Domain.Value = domain - } else { - domain = config.Domain.Value - } - if domain == "" { - // Error vs warning - empty value must stop execution - resp.Diagnostics.AddError( - "Invalid provider `domain`.", - "`domain` cannot be an empty string.", - ) - return - } - - // User must provide a password to the provider - var apiKey string - if config.ApiKey.Unknown { - // Cannot connect to client with an unknown value - resp.Diagnostics.AddError( - "Invalid provider API key.", - "Cannot use unknown value as `appkey`.", - ) - return - } - - if config.ApiKey.Null { - apiKey = os.Getenv("KEYFACTOR_APPKEY") - config.ApiKey.Value = apiKey - } else { - apiKey = config.ApiKey.Value - } - - // User must provide a password to the provider - var password string - if config.Password.Unknown { - // Cannot connect to client with an unknown value - resp.Diagnostics.AddError( - "Invalid provider `password`.", - "Cannot use unknown value as `password`", - ) - return - } - - if config.Password.Null { - password = os.Getenv("KEYFACTOR_PASSWORD") - config.Password.Value = password - } else { - password = config.Password.Value - } - - if password == "" && apiKey == "" { - // Error vs warning - empty value must stop execution - resp.Diagnostics.AddError( - "Invlaid provider credentials. ", - "`password` and `appkey` cannot both be empty string.", - ) - return - } - - // User must specify a host - var host string - if config.Hostname.Unknown { - // Cannot connect to client with an unknown value - resp.Diagnostics.AddError( - "Invalid provider `host`.", - "Cannot use unknown value as `host`.", - ) - return - } - - if config.Hostname.Null { - host = os.Getenv("KEYFACTOR_HOSTNAME") - config.Hostname.Value = host - } else { - host = config.Hostname.Value - } - - if host == "" { - // Error vs warning - empty value must stop execution - resp.Diagnostics.AddError( - "Invalid provider `host`.", - "Provider `host` cannot be an empty string.", - ) - return - } - - // Create a new Keyfactor client and set it to the provider client - var clientAuth api.AuthConfig - clientAuth.Username = config.Username.Value - clientAuth.Password = config.Password.Value - //clientAuth.ApiKey = config.ApiKey.Value //TODO: Add API key support - clientAuth.Domain = config.Domain.Value - clientAuth.Hostname = config.Hostname.Value - - c, err := api.NewKeyfactorClient(&clientAuth) - - if err != nil { - resp.Diagnostics.AddError( - "Unable to create client", - "Unable to create Keyfactor GO client client:\n\n"+err.Error(), - ) - return - } - - p.client = c - p.configured = true -} - -// GetResources - Defines provider resources -func (p *provider) GetResources(_ context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { - return map[string]tfsdk.ResourceType{ - "keyfactor_identity": resourceSecurityIdentityType{}, - "keyfactor_certificate": resourceKeyfactorCertificateType{}, - "keyfactor_certificate_store": resourceCertificateStoreType{}, - "keyfactor_certificate_deployment": resourceKeyfactorCertificateDeploymentType{}, - "keyfactor_role": resourceSecurityRoleType{}, - "keyfactor_template_role_binding": resourceCertificateTemplateRoleBindingType{}, - }, nil -} - -// GetDataSources - Defines provider data sources -func (p *provider) GetDataSources(_ context.Context) (map[string]tfsdk.DataSourceType, diag.Diagnostics) { - return map[string]tfsdk.DataSourceType{ - "keyfactor_certificate": dataSourceCertificateType{}, - "keyfactor_certificate_store": dataSourceCertificateStoreType{}, - "keyfactor_certificate_template": dataSourceCertificateTemplateType{}, - "keyfactor_role": dataSourceSecurityRoleType{}, - "keyfactor_identity": dataSourceSecurityIdentityType{}, - }, nil -} - -//// Utility functions -func boolToPointer(b bool) *bool { - return &b -} - -func intToPointer(i int) *int { - if i == 0 { - return nil - } - return &i -} - -func stringToPointer(s string) *string { - if s == "" { - return nil - } - return &s -} diff --git a/keyfactor/provider_test.go b/keyfactor/provider_test.go deleted file mode 100644 index d4ae524d..00000000 --- a/keyfactor/provider_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package keyfactor - -import ( - "testing" - - "github.com/hashicorp/terraform-plugin-framework/providerserver" - "github.com/hashicorp/terraform-plugin-go/tfprotov6" -) - -// testAccProtoV6ProviderFactories are used to instantiate a provider during -// acceptance testing. The factory function will be invoked for every Terraform -// CLI command executed to create a provider server to which the CLI can -// reattach. -var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){ - "keyfactor": providerserver.NewProtocol6WithError(New()), -} - -func testAccPreCheck(t *testing.T) { - // You can add code here to run prior to any test case execution, for example assertions - // about the appropriate environment variables being set are common to see in a pre-check - // function. -} diff --git a/keyfactor/resource_keyfactor_certificate.go b/keyfactor/resource_keyfactor_certificate.go deleted file mode 100644 index 328e81e2..00000000 --- a/keyfactor/resource_keyfactor_certificate.go +++ /dev/null @@ -1,757 +0,0 @@ -package keyfactor - -import ( - "context" - "crypto/ecdsa" - rsa2 "crypto/rsa" - "crypto/x509" - "encoding/pem" - "fmt" - "github.com/Keyfactor/keyfactor-go-client/api" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" - "log" - "strconv" -) - -type resourceKeyfactorCertificateType struct{} - -func (r resourceKeyfactorCertificateType) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfsdk.Schema{ - Attributes: map[string]tfsdk.Attribute{ - "csr": { - Type: types.StringType, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "Base-64 encoded certificate signing request (CSR)", - }, - "key_password": { - Type: types.StringType, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Sensitive: true, - Description: "Password to protect certificate and private key with", - }, - "subject": { - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "KeyfactorCertificate subject", - Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ - "subject_common_name": { - Type: types.StringType, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "Subject common name for new certificate", - }, - "subject_locality": { - Type: types.StringType, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "Subject locality for new certificate", - }, - "subject_organization": { - Type: types.StringType, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "Subject organization for new certificate", - }, - "subject_state": { - Type: types.StringType, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "Subject state for new certificate", - }, - "subject_country": { - Type: types.StringType, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "Subject country for new certificate", - }, - "subject_organizational_unit": { - Type: types.StringType, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "Subject organizational unit for new certificate", - }, - }), - }, - "certificate_authority": { - Type: types.StringType, - Required: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - //DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // return strings.EqualFold(old, new) - //}, - Description: "Name of certificate authority to deploy certificate with Ex: Example Company CA 1", - }, - "certificate_template": { - Type: types.StringType, - Required: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "Short name of certificate template to be deployed", - }, - "dns_sans": { - Type: types.ListType{ElemType: types.StringType}, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "List of DNS names to use as subjects of the certificate", - //DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // // For some reason Terraform detects this particular function as having drift; this function - // // gives us a definitive answer. - // return !d.HasChange(k) - //}, - }, - "uri_sans": { - Type: types.ListType{ElemType: types.StringType}, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "List of URIs to use as subjects of the certificate", - //DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // // For some reason Terraform detects this particular function as having drift; this function - // // gives us a definitive answer. - // return !d.HasChange(k) - //}, - }, - "ip_sans": { - Type: types.ListType{ElemType: types.StringType}, - Optional: true, - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - Description: "List of DNS names to use as subjects of the certificate", - //DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // // For some reason Terraform detects this particular function as having drift; this function - // // gives us a definitive answer. - // return !d.HasChange(k) - //}, - }, - "metadata": { - Type: types.MapType{ - ElemType: types.StringType, - }, - Optional: true, - Description: "Metadata key-value pairs to be attached to certificate", - }, - "serial_number": { - Type: types.StringType, - Computed: true, - Description: "Serial number of newly enrolled certificate", - }, - "issuer_dn": { - Type: types.StringType, - Computed: true, - Description: "Issuer distinguished name that signed the certificate", - }, - "thumbprint": { - Type: types.StringType, - Computed: true, - Description: "Thumbprint of newly enrolled certificate", - }, - "id": { - Type: types.Int64Type, - Computed: true, - Description: "Keyfactor certificate ID", - }, - "collection_id": { - Type: types.Int64Type, - Required: false, - Optional: true, - Description: "Optional certificate collection identifier used to ensure user access to the certificate.", - }, - "keyfactor_request_id": { - Type: types.Int64Type, - Computed: true, - Description: "Keyfactor request ID necessary for deploying certificate", - }, - "certificate_pem": { - Type: types.StringType, - Computed: true, - Description: "PEM formatted certificate", - }, - "certificate_chain": { - Type: types.StringType, - Computed: true, - Description: "PEM formatted certificate chain", - }, - "private_key": { - Type: types.StringType, - Computed: true, - Sensitive: true, - Description: "PEM formatted PKCS#1 private key imported if cert_template has KeyRetention set to a value other than None, and the certificate was not enrolled using a CSR.", - }, - }, - }, nil -} - -func (r resourceKeyfactorCertificateType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourceKeyfactorCertificate{ - p: *(p.(*provider)), - }, nil -} - -type resourceKeyfactorCertificate struct { - p provider -} - -func (r resourceKeyfactorCertificate) Create(ctx context.Context, request tfsdk.CreateResourceRequest, response *tfsdk.CreateResourceResponse) { - if !r.p.configured { - response.Diagnostics.AddError( - "Provider not configured", - "The provider hasn't been configured before apply, likely because it depends on an unknown value from another resource. This leads to weird stuff happening, so we'd prefer if you didn't do that. Thanks!", - ) - return - } - - // Retrieve values from plan - var plan KeyfactorCertificate - diags := request.Plan.Get(ctx, &plan) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Generate API request body from plan - - kfClient := r.p.client - - certificateId := plan.ID.Value - ctx = tflog.SetField(ctx, "certificate_id", certificateId) - tflog.Info(ctx, "Create called on certificate resource") - - //sans := plan.SANs - //metadata := plan.Metadata.Elems - csr := plan.CSR.Value - if (plan.CSR.IsNull() && plan.Subject.IsNull()) || (!plan.CSR.IsNull() && !plan.Subject.IsNull()) || (csr == "" && plan.Subject.IsNull()) { - response.Diagnostics.AddError( - "Invalid certificate resource definition.", - "You must provide either a CSR or a Subject to create a certificate.", - ) - return - } - - var dnsSANs []string - var ipSANs []string - var uriSANs []string - var metadata map[string]interface{} - diags = plan.DNSSANs.ElementsAs(ctx, &dnsSANs, true) - diags = plan.IPSANs.ElementsAs(ctx, &ipSANs, true) - diags = plan.URISANs.ElementsAs(ctx, &uriSANs, true) - diags = plan.Metadata.ElementsAs(ctx, &metadata, true) - - sans := append(dnsSANs, ipSANs...) - sans = append(sans, uriSANs...) - - if !plan.CSR.IsNull() && csr != "" { - tflog.Debug(ctx, "Creating certificate from CSR.") - - tflog.Debug(ctx, fmt.Sprintf("Creating certificate with SANs: %s", sans)) - CSRArgs := &api.EnrollCSRFctArgs{ - CSR: csr, - CertificateAuthority: plan.CertificateAuthority.Value, - Template: plan.CertificateTemplate.Value, - IncludeChain: true, - CertFormat: "PEM", // Retrieve certificate in READ - SANs: &api.SANs{ - IP4: ipSANs, - IP6: nil, //TODO: ipv6 SANs support - DNS: dnsSANs, - URI: uriSANs, - }, - Metadata: metadata, - } - tflog.Trace(ctx, "Passing args to Keyfactor API.", map[string]interface{}{ - "args": CSRArgs, - }) - enrollResponse, err := kfClient.EnrollCSR(CSRArgs) - if err != nil { - response.Diagnostics.AddError( - "Error creating certificate.", - "Could not create certificate in Keyfactor: "+err.Error(), - ) - return - } - - // Set state - var result = KeyfactorCertificate{ - ID: types.Int64{Value: int64(enrollResponse.CertificateInformation.KeyfactorID)}, - CSR: types.String{Value: csr}, - Subject: plan.Subject, - DNSSANs: plan.DNSSANs, - IPSANs: plan.IPSANs, - URISANs: plan.URISANs, - SerialNumber: types.String{Value: enrollResponse.CertificateInformation.SerialNumber}, - IssuerDN: types.String{Value: enrollResponse.CertificateInformation.IssuerDN}, - Thumbprint: types.String{Value: enrollResponse.CertificateInformation.Thumbprint}, - PEM: types.String{Value: enrollResponse.CertificateInformation.Certificates[0]}, - PEMChain: types.String{Value: enrollResponse.CertificateInformation.Certificates[1]}, - PrivateKey: types.String{Value: plan.PrivateKey.Value}, - KeyPassword: plan.KeyPassword, - CertificateAuthority: plan.CertificateAuthority, - CertificateTemplate: plan.CertificateTemplate, - RequestId: types.Int64{Value: int64(enrollResponse.CertificateInformation.KeyfactorRequestID)}, - Metadata: plan.Metadata, - CollectionId: plan.CollectionId, - } - - diags = response.State.Set(ctx, result) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - } else { - subject := make(map[string]interface{}) - subjectObj := plan.Subject.Attrs - for k, v := range subjectObj { - subject[k] = v.String() - } - PFXArgs := &api.EnrollPFXFctArgs{ - CustomFriendlyName: "Terraform", - Password: plan.KeyPassword.Value, - PopulateMissingValuesFromAD: false, //TODO: Add support for this - CertificateAuthority: plan.CertificateAuthority.Value, - Template: plan.CertificateTemplate.Value, - IncludeChain: true, //TODO: Add support for this - CertFormat: "STORE", // Get certificate from data source - SANs: &api.SANs{ - IP4: ipSANs, - IP6: nil, //TODO: ipv6 SANs support - DNS: dnsSANs, - URI: uriSANs, - }, - Metadata: metadata, - Subject: &api.CertificateSubject{ - SubjectCommonName: subject["subject_common_name"].(string), - SubjectLocality: subject["subject_locality"].(string), - SubjectOrganization: subject["subject_organization"].(string), - SubjectCountry: subject["subject_country"].(string), - SubjectOrganizationalUnit: subject["subject_organizational_unit"].(string), - SubjectState: subject["subject_state"].(string), - }, - } - tflog.Debug(ctx, fmt.Sprintf("Creating PFX certificate %s on Keyfactor.", PFXArgs.Subject.SubjectCommonName)) - enrollResponse, err := kfClient.EnrollPFX(PFXArgs) - if err != nil { - response.Diagnostics.AddError( - "Error creating certificate.", - fmt.Sprintf("Could not create certificate %s on Keyfactor: "+err.Error(), PFXArgs.Subject.SubjectCommonName), - ) - return - } - - enrolledId := enrollResponse.CertificateInformation.KeyfactorID - // Download and assign certificates to proper location - leaf, chain, pKey, dErr := downloadCertificate(enrolledId, r.p.client, plan.KeyPassword.Value, csr != "") - if dErr != nil { - response.Diagnostics.AddError( - "Error reading Keyfactor certificate.", - fmt.Sprintf("Could not retrieve certificate '%s' from Keyfactor: "+dErr.Error(), certificateId), - ) - } - - // Set state - var result = KeyfactorCertificate{ - ID: types.Int64{Value: int64(enrolledId)}, - CSR: plan.CSR, - Subject: plan.Subject, - DNSSANs: plan.DNSSANs, - IPSANs: plan.IPSANs, - URISANs: plan.URISANs, - SerialNumber: types.String{Value: enrollResponse.CertificateInformation.SerialNumber}, - IssuerDN: types.String{Value: enrollResponse.CertificateInformation.IssuerDN}, - Thumbprint: types.String{Value: enrollResponse.CertificateInformation.Thumbprint}, - PEM: types.String{Value: leaf}, - PEMChain: types.String{Value: chain}, - PrivateKey: types.String{Value: pKey}, - KeyPassword: plan.KeyPassword, - CertificateAuthority: plan.CertificateAuthority, - CertificateTemplate: plan.CertificateTemplate, - RequestId: types.Int64{Value: int64(enrollResponse.CertificateInformation.KeyfactorRequestID)}, - Metadata: plan.Metadata, - CollectionId: plan.CollectionId, - } - - diags = response.State.Set(ctx, result) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - } - -} - -func (r resourceKeyfactorCertificate) Read(ctx context.Context, request tfsdk.ReadResourceRequest, response *tfsdk.ReadResourceResponse) { - var state KeyfactorCertificate - diags := request.State.Get(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - tflog.Info(ctx, "Read called on certificate resource") - certificateId := state.ID.Value - certificateIdInt := int(certificateId) - - tflog.SetField(ctx, "certificate_id", certificateId) - - // Get certificate context - args := &api.GetCertificateContextArgs{ - IncludeMetadata: boolToPointer(true), - IncludeLocations: boolToPointer(true), - CollectionId: nil, - Id: certificateIdInt, - } - _, err := r.p.client.GetCertificateContext(args) - if err != nil { - response.Diagnostics.AddError( - "Error reading Keyfactor certificate.", - fmt.Sprintf("Could not retrieve certificate '%s' from Keyfactor: "+err.Error(), certificateId), - ) - return - } - - // Get the password out of current schema - csr := state.CSR.Value - - // Download and assign certificates to proper location - //leaf, chain, pKey, dErr := downloadCertificate(certificateIdInt, r.p.client, state.KeyPassword.Value, csr != "") - _, _, _, dErr := downloadCertificate(certificateIdInt, r.p.client, state.KeyPassword.Value, csr != "") - if dErr != nil { - response.Diagnostics.AddError( - "Error reading Keyfactor certificate.", - fmt.Sprintf("Could not retrieve certificate '%s' from Keyfactor: "+dErr.Error(), certificateId), - ) - } - - //var result = KeyfactorCertificate{ - // ID: types.Int64{Value: state.ID.Value}, - // CSR: types.String{Value: csr}, - // Subject: state.Subject, - // DNSSANs: state.DNSSANs, - // IPSANs: state.IPSANs, - // URISANs: state.URISANs, - // SerialNumber: state.SerialNumber, - // IssuerDN: state.IssuerDN, - // Thumbprint: state.Thumbprint, - // PEM: types.String{Value: leaf}, - // PEMChain: types.String{Value: chain}, - // PrivateKey: types.String{Value: pKey}, - // KeyPassword: types.String{Value: password}, - // //PEM: state.PEM, - // //PEMChain: state.PEMChain, - // //PrivateKey: state.PrivateKey, - // //KeyPassword: state.KeyPassword, - // CertificateAuthority: state.CertificateAuthority, - // CertificateTemplate: state.CertificateTemplate, - // RequestId: state.RequestId, - // Metadata: state.Metadata, - //} - - // Set state - diags = response.State.Set(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } -} - -func (r resourceKeyfactorCertificate) Update(ctx context.Context, request tfsdk.UpdateResourceRequest, response *tfsdk.UpdateResourceResponse) { - // Get plan values - var plan KeyfactorCertificate - diags := request.Plan.Get(ctx, &plan) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Get current state - var state KeyfactorCertificate - diags = request.State.Get(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - csr := plan.CSR.Value - if csr != "" { - tflog.Debug(ctx, "Creating certificate from CSR.") - - var dnsSANs []string - var ipSANs []string - var uriSANs []string - var planMetadata map[string]string - var stateMetadata map[string]string - diags = state.DNSSANs.ElementsAs(ctx, &dnsSANs, true) - diags = state.IPSANs.ElementsAs(ctx, &ipSANs, true) - diags = state.URISANs.ElementsAs(ctx, &uriSANs, true) - diags = plan.Metadata.ElementsAs(ctx, &planMetadata, false) - diags = state.Metadata.ElementsAs(ctx, &stateMetadata, false) - - //diags = request.Plan.Get(ctx, &metadata) - - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - sans := append(dnsSANs, ipSANs...) - sans = append(sans, uriSANs...) - - tflog.Debug(ctx, fmt.Sprintf("Creating certificate with SANs: %s", sans)) - metaInterface := make(map[string]interface{}) - for k, v := range planMetadata { - metaInterface[k] = v - } - if !plan.Metadata.Equal(state.Metadata) { - tflog.Debug(ctx, "Metadata is updated. Attempting to update metadata on Keyfactor.") - - err := r.p.client.UpdateMetadata( - &api.UpdateMetadataArgs{ - CertID: int(state.ID.Value), - Metadata: metaInterface, - }) - if err != nil { - response.Diagnostics.AddError("Certificate metadata update error.", fmt.Sprintf("Could not update cert '%s''s metadata on Keyfactor: "+err.Error(), state.ID.Value)) - return - } - - } - - // Set state - var result = KeyfactorCertificate{ - ID: types.Int64{Value: state.ID.Value}, - CSR: types.String{Value: csr}, - Subject: state.Subject, - DNSSANs: state.DNSSANs, - IPSANs: state.IPSANs, - URISANs: state.URISANs, - SerialNumber: state.SerialNumber, - IssuerDN: state.IssuerDN, - Thumbprint: state.Thumbprint, - PEM: state.PEM, - PEMChain: state.PEMChain, - PrivateKey: state.PrivateKey, - KeyPassword: state.KeyPassword, - CertificateAuthority: state.CertificateAuthority, - CertificateTemplate: state.CertificateTemplate, - RequestId: state.RequestId, - Metadata: plan.Metadata, - } - - diags = response.State.Set(ctx, result) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - } else { - // Set state - var result = KeyfactorCertificate{ - ID: types.Int64{Value: state.ID.Value}, - CSR: state.CSR, - Subject: state.Subject, - DNSSANs: state.DNSSANs, - IPSANs: state.IPSANs, - URISANs: state.URISANs, - SerialNumber: state.SerialNumber, - IssuerDN: state.IssuerDN, - Thumbprint: state.Thumbprint, - PEM: state.PEM, - PEMChain: state.PEMChain, - PrivateKey: state.PrivateKey, - KeyPassword: state.KeyPassword, - CertificateAuthority: state.CertificateAuthority, - CertificateTemplate: state.CertificateTemplate, - RequestId: state.RequestId, - Metadata: plan.Metadata, - } - - diags = response.State.Set(ctx, result) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - } -} - -func (r resourceKeyfactorCertificate) Delete(ctx context.Context, request tfsdk.DeleteResourceRequest, response *tfsdk.DeleteResourceResponse) { - var state KeyfactorCertificate - diags := request.State.Get(ctx, &state) - kfClient := r.p.client - - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Get order ID from state - certificateId := state.ID.Value - tflog.SetField(ctx, "certificate_id", certificateId) - - // Delete order by calling API - log.Println("[INFO] Deleting certificate resource") - - // When Terraform Destroy is called, we want Keyfactor to revoke the certificate. - - tflog.Info(ctx, fmt.Sprintf("Revoking certificate %v in Keyfactor", certificateId)) - - revokeArgs := &api.RevokeCertArgs{ - CertificateIds: []int{int(certificateId)}, // Certificate ID expects array of integers - Reason: 5, // reason = 5 means Cessation of Operation - Comment: "Terraform destroy called on provider with associated cert ID", - } - - err := kfClient.RevokeCert(revokeArgs) - if err != nil { - response.Diagnostics.AddError("Certificate revocation error.", fmt.Sprintf("Could not revoke cert '%s' on Keyfactor: "+err.Error(), certificateId)) - } - - // Remove resource from state - response.State.RemoveResource(ctx) - -} - -func (r resourceKeyfactorCertificate) ImportState(ctx context.Context, request tfsdk.ImportResourceStateRequest, response *tfsdk.ImportResourceStateResponse) { - var state KeyfactorCertificate - if response.Diagnostics.HasError() { - return - } - - tflog.Info(ctx, "Read called on certificate resource") - certificateId := request.ID - certificateIdInt, err := strconv.Atoi(certificateId) - if err != nil { - response.Diagnostics.AddError("Import error.", fmt.Sprintf("Could not convert cert ID '%s' to integer: "+err.Error(), certificateId)) - return - } - - tflog.SetField(ctx, "certificate_id", certificateId) - - // Get certificate context - args := &api.GetCertificateContextArgs{ - IncludeMetadata: boolToPointer(true), - IncludeLocations: boolToPointer(true), - CollectionId: nil, - Id: certificateIdInt, - } - certificateData, err := r.p.client.GetCertificateContext(args) - if err != nil { - response.Diagnostics.AddError( - "Error reading Keyfactor certificate.", - fmt.Sprintf("Could not retrieve certificate '%s' from Keyfactor: "+err.Error(), certificateId), - ) - return - } - - // Get the password out of current schema - password := "" - csr := "" - - // Download and assign certificates to proper location - priv, leaf, chain, dErr := downloadCertificate(certificateData.Id, r.p.client, password, csr != "") - if dErr != nil { - response.Diagnostics.AddError( - "Error reading Keyfactor certificate.", - fmt.Sprintf("Could not retrieve certificate '%s' from Keyfactor: "+dErr.Error(), certificateId), - ) - return - } - - var result = KeyfactorCertificate{ - ID: types.Int64{Value: state.ID.Value}, - CSR: types.String{Value: csr}, - Subject: state.Subject, - DNSSANs: state.DNSSANs, - IPSANs: state.IPSANs, - URISANs: state.URISANs, - SerialNumber: state.SerialNumber, - IssuerDN: state.IssuerDN, - Thumbprint: state.Thumbprint, - PEM: types.String{Value: leaf}, - PEMChain: types.String{Value: chain}, - PrivateKey: types.String{Value: priv}, - KeyPassword: types.String{Value: password}, - CertificateAuthority: state.CertificateAuthority, - CertificateTemplate: state.CertificateTemplate, - RequestId: state.RequestId, - Metadata: state.Metadata, - } - - // Set state - diags := response.State.Set(ctx, &result) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } -} - -func downloadCertificate(id int, kfClient *api.Client, password string, csrEnrollment bool) (string, string, string, error) { - certificateContext, err := kfClient.GetCertificateContext(&api.GetCertificateContextArgs{Id: id}) - if err != nil { - return "", "", "", err - } - - template, err := kfClient.GetTemplate(certificateContext.TemplateId) - if err != nil { - return "", "", "", err - } - - recoverable := false - - if template.KeyRetention != "None" { - recoverable = true - } - - var privPem []byte - var leafPem []byte - var chainPem []byte - - if !recoverable || csrEnrollment { - - leaf, chain, err := kfClient.DownloadCertificate(id, "", "", "") - if err != nil { - return "", "", "", err - } - - // Encode DER to PEM - leafPem = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leaf.Raw}) - for _, i := range chain { - chainPem = append(chainPem, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: i.Raw})...) - } - - } else { - - priv, leaf, chain, err := kfClient.RecoverCertificate(id, "", "", "", password) - if err != nil { - return "", "", "", err - } - if err != nil { - return "", "", "", err - } - - // Encode DER to PEM - leafPem = pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: leaf.Raw}) - for _, i := range chain { - chainPem = append(chainPem, pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: i.Raw})...) - } - - // Figure out the format of the private key, then encode it to PEM - rsa, ok := priv.(*rsa2.PrivateKey) - if ok { - buf := x509.MarshalPKCS1PrivateKey(rsa) - if len(buf) > 0 { - privPem = pem.EncodeToMemory(&pem.Block{Bytes: buf, Type: "RSA PRIVATE KEY"}) - } - } - - ecc, ok := priv.(*ecdsa.PrivateKey) - if ok { - // We don't really care about the error here. An error just means that the key will be blank which isn't a - // reason to fail - buf, _ := x509.MarshalECPrivateKey(ecc) - if len(buf) > 0 { - privPem = pem.EncodeToMemory(&pem.Block{Bytes: buf, Type: "EC PRIVATE KEY"}) - } - } - } - - return string(leafPem), string(chainPem), string(privPem), nil -} diff --git a/keyfactor/resource_keyfactor_certificate_deploy.go b/keyfactor/resource_keyfactor_certificate_deploy.go deleted file mode 100644 index e9cdb7d8..00000000 --- a/keyfactor/resource_keyfactor_certificate_deploy.go +++ /dev/null @@ -1,471 +0,0 @@ -package keyfactor - -import ( - "context" - "crypto/sha256" - "fmt" - "github.com/Keyfactor/keyfactor-go-client/api" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" - "strings" - "time" -) - -type resourceKeyfactorCertificateDeploymentType struct{} - -func (r resourceKeyfactorCertificateDeploymentType) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfsdk.Schema{ - Attributes: map[string]tfsdk.Attribute{ - "id": { - Type: types.StringType, - Computed: true, - Description: "A unique identifier for this certificate deployment.", - }, - "certificate_id": { - Type: types.Int64Type, - Required: true, - Description: "Keyfactor certificate ID", - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - }, - "certificate_store_id": { - Type: types.StringType, - Required: true, - Description: "A string containing the GUID for the certificate store to which the certificate should be added.", - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - }, - "certificate_alias": { - Type: types.StringType, - Required: true, - Description: "A string providing an alias to be used for the certificate upon entry into the certificate store. The function of the alias varies depending on the certificate store type. Please ensure that the alias is lowercase, or problems can arise in Terraform Plan.", - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - }, - "key_password": { - Type: types.StringType, - Optional: true, - Sensitive: true, - Description: "Password that protects PFX certificate, if the certificate was enrolled using PFX enrollment, or is password protected in general. This value cannot change, and Terraform will throw an error if a change is attempted.", - PlanModifiers: []tfsdk.AttributePlanModifier{tfsdk.RequiresReplace()}, - }, - }, - }, nil -} - -func (r resourceKeyfactorCertificateDeploymentType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourceKeyfactorCertificateDeployment{ - p: *(p.(*provider)), - }, nil -} - -type resourceKeyfactorCertificateDeployment struct { - p provider -} - -func (r resourceKeyfactorCertificateDeployment) Create(ctx context.Context, request tfsdk.CreateResourceRequest, response *tfsdk.CreateResourceResponse) { - if !r.p.configured { - response.Diagnostics.AddError( - "Provider not configured", - "The provider hasn't been configured before apply, likely because it depends on an unknown value from another resource. This leads to weird stuff happening, so we'd prefer if you didn't do that. Thanks!", - ) - return - } - - // Retrieve values from plan - var plan KeyfactorCertificateDeployment - diags := request.Plan.Get(ctx, &plan) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Generate API request body from plan - - kfClient := r.p.client - - certificateId := plan.CertificateId.Value - certificateIdInt := int(certificateId) - storeId := plan.StoreId.Value - certificateAlias := plan.CertificateAlias.Value - keyPassword := plan.KeyPassword.Value - hid := fmt.Sprintf("%v-%s-%s", certificateId, storeId, certificateAlias) - - ctx = tflog.SetField(ctx, "certificate_id", certificateId) - ctx = tflog.SetField(ctx, "certificate_store_id", storeId) - ctx = tflog.SetField(ctx, "certificate_alias", certificateAlias) - tflog.Info(ctx, "Create called on certificate deployment resource") - - //sans := plan.SANs - //metadata := plan.Metadata.Elems - vErr := validateCertificatesInStore(ctx, kfClient, certificateIdInt, storeId, 1) - if vErr == nil { - response.Diagnostics.AddWarning( - "Duplicate deployment.", - fmt.Sprintf("Certificate '%v' is already deployed to '%s (%s)'", certificateId, storeId, certificateAlias), - ) - } else { - addErr := addCertificateToStore(ctx, kfClient, certificateIdInt, certificateAlias, keyPassword, storeId) - if addErr != nil { - response.Diagnostics.AddError( - "Certificate deployment error", - fmt.Sprintf("Unknown error during deploy of certificate '%v'(%s) to store '%s': "+addErr.Error(), certificateId, certificateAlias, storeId), - ) - } - if response.Diagnostics.HasError() { - return - } - - vErr2 := validateCertificatesInStore(ctx, kfClient, certificateIdInt, storeId, 5) - if vErr2 != nil { - response.Diagnostics.AddError( - "Deployment validation error.", - fmt.Sprintf("Unknown error during validation of deploy of certificate '%s' to store '%s (%s)': "+vErr.Error(), certificateId, storeId, certificateAlias), - ) - } - if response.Diagnostics.HasError() { - return - } - } - - // Set state - var result = KeyfactorCertificateDeployment{ - ID: types.String{Value: fmt.Sprintf("%x", sha256.Sum256([]byte(hid)))}, - CertificateId: plan.CertificateId, - StoreId: plan.StoreId, - CertificateAlias: plan.CertificateAlias, - KeyPassword: plan.KeyPassword, - } - - diags = response.State.Set(ctx, result) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - -} - -func (r resourceKeyfactorCertificateDeployment) Read(ctx context.Context, request tfsdk.ReadResourceRequest, response *tfsdk.ReadResourceResponse) { - var state KeyfactorCertificateDeployment - diags := request.State.Get(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - kfClient := r.p.client - - certificateId := state.CertificateId.Value - certificateIdInt := int(certificateId) - storeId := state.StoreId.Value - //storeIdInt := int(storeId) - certificateAlias := state.CertificateAlias.Value - //keyPassword := state.KeyPassword.Value - //hid := fmt.Sprintf("%s-%s-%s", certificateId, storeId, certificateAlias) - - ctx = tflog.SetField(ctx, "certificate_id", certificateId) - ctx = tflog.SetField(ctx, "certificate_store_id", storeId) - ctx = tflog.SetField(ctx, "certificate_alias", certificateAlias) - tflog.Info(ctx, "Create called on certificate deployment resource") - - // Get certificate context - args := &api.GetCertificateContextArgs{ - IncludeLocations: boolToPointer(true), - Id: certificateIdInt, - } - certificateData, err := kfClient.GetCertificateContext(args) - if err != nil { - response.Diagnostics.AddError( - "Deployment read error.", - fmt.Sprintf("Unknown error during read status of deployment of certificate '%s' to store '%s (%s)': "+err.Error(), certificateId, storeId, certificateAlias), - ) - } - locations := certificateData.Locations - for _, location := range locations { - tflog.Debug(ctx, fmt.Sprintf("Certificate %v stored in location: %v", certificateIdInt, location)) - } - - // Set state - var result = KeyfactorCertificateDeployment{ - ID: state.ID, - CertificateId: state.CertificateId, - StoreId: state.StoreId, - CertificateAlias: state.CertificateAlias, - KeyPassword: state.KeyPassword, - } - - diags = response.State.Set(ctx, result) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } -} - -func (r resourceKeyfactorCertificateDeployment) Update(ctx context.Context, request tfsdk.UpdateResourceRequest, response *tfsdk.UpdateResourceResponse) { - // Get plan values - var plan KeyfactorCertificate - diags := request.Plan.Get(ctx, &plan) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Get current state - var state KeyfactorCertificate - diags = request.State.Get(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // API Actions - - // Set state - tflog.Error(ctx, "Update called on certificate deployment resource") - response.Diagnostics.AddError( - "Certificate deployment updates not implemented.", - fmt.Sprintf("Error, only create and delete actions are supported for certificate deployments."), - ) - if response.Diagnostics.HasError() { - return - } -} - -func (r resourceKeyfactorCertificateDeployment) Delete(ctx context.Context, request tfsdk.DeleteResourceRequest, response *tfsdk.DeleteResourceResponse) { - var state KeyfactorCertificateDeployment - diags := request.State.Get(ctx, &state) - - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Vars and logging contexts - kfClient := r.p.client - - certificateId := state.CertificateId.Value - //certificateIdInt := int(certificateId) - storeId := state.StoreId.Value - //storeIdInt := int(storeId) - certificateAlias := state.CertificateAlias.Value - //keyPassword := state.KeyPassword.Value - //hid := fmt.Sprintf("%s-%s-%s", certificateId, storeId, certificateAlias) - - if certificateAlias == "" { - // If no alias is provided then lookup the cert ID in keyfactor and use the alias from there - lookupCertResp, lkErr := kfClient.GetCertificateContext(&api.GetCertificateContextArgs{Id: int(certificateId)}) - if lkErr != nil { - response.Diagnostics.AddWarning( - "Certificate removal error.", - fmt.Sprintf("Error looking up certificate '%s' in Keyfactor: "+lkErr.Error(), certificateId), - ) - response.State.RemoveResource(ctx) - return - } - certificateAlias = lookupCertResp.Thumbprint - } - ctx = tflog.SetField(ctx, "certificate_id", certificateId) - ctx = tflog.SetField(ctx, "certificate_store_id", storeId) - ctx = tflog.SetField(ctx, "certificate_alias", certificateAlias) - tflog.Info(ctx, "Delete called on certificate deployment resource") - - // Remove certificate from store - var diff []api.CertificateStore - certStoreRequest := api.CertificateStore{ - CertificateStoreId: storeId, - Alias: certificateAlias, - } - diff = append(diff, certStoreRequest) - - // Remove resource from state - err := removeCertificateAliasFromStore(ctx, kfClient, &diff) - if err != nil { - if strings.Contains(err.Error(), "not found") { - response.Diagnostics.AddWarning( - "Certificate deployment not found.", - fmt.Sprintf("Certificate deployment '%v' to store '%s (%s)' not found, removing from state.", certificateId, storeId, certificateAlias), - ) - } else { - response.Diagnostics.AddError( - "Certificate deployment error", - fmt.Sprintf("Unknown error during removal of certificate '%s' from store '%s (%s)': "+err.Error(), certificateId, storeId, certificateAlias), - ) - } - - } - - if response.Diagnostics.HasError() { - return - } - response.State.RemoveResource(ctx) -} - -func (r resourceKeyfactorCertificateDeployment) ImportState(ctx context.Context, request tfsdk.ImportResourceStateRequest, response *tfsdk.ImportResourceStateResponse) { - tflog.Error(ctx, "Import called on certificate deployment resource") - response.Diagnostics.AddError( - "Certificate deployment imports not implemented.", - fmt.Sprintf("Error, only create and delete actions are supported for certificate deployments."), - ) - if response.Diagnostics.HasError() { - return - } -} - -//func setCertificatesInStore(ctx context.Context, conn *api.Client, certificateId int, keyPassword string, storeId int, storeAlias string) error { -// -// tflog.Debug(ctx, fmt.Sprintf("Setting certificate %v in Keyfactor store %v", certificateId, storeId)) -// // First, blindly add the certificate to each of the certificate storeId found in storeList. -// err := addCertificateToStore(conn, certificateId, keyPassword, storeId, storeAlias) -// if err != nil { -// return err -// } -// -// // Then, compile a list of storeId that the certificate is found in, and figure out the delta -// args := &api.GetCertificateContextArgs{ -// IncludeLocations: boolToPointer(true), -// Id: certificateId, -// } -// certificateData, err := conn.GetCertificateContext(args) -// if err != nil { -// return err -// } -// locations := certificateData.Locations -// expectedStores := make([]string, len(storeId)) -// -// // Want to find the elements in locations that are not in storeId -// // We also want to retain the alias -// list := make(map[string]struct{}, len(storeId)) -// for i, x := range storeId { -// j := x.(map[string]interface{}) -// -// storeId := j["certificate_store_id"].(string) -// list[storeId] = struct{}{} -// -// // Since we're already looping through the store IDs, place them in a more readable data structre for later use -// expectedStores[i] = storeId -// } -// -// // The elements of diff should be removed -// // Also, removing a certificate from a certificate store implies that the certificate is currently in the store. -// var diff []api.CertificateStore -// for _, x := range locations { -// if _, found := list[x.CertStoreId]; !found { -// temp := api.CertificateStore{ -// CertificateStoreId: x.CertStoreId, -// Alias: x.Alias, -// } -// diff = append(diff, temp) -// } -// } -// -// if len(diff) > 0 { -// err = removeCertificateAliasFromStore(conn, &diff) -// if err != nil { -// return err -// } -// } -// -// // Finally, Keyfactor tends to take a hot second to enact these changes despite being told to make them immediately. -// // Block for a long time until the changes are validated. -// err = validateCertificatesInStore(conn, expectedStores, certificateId) -// if err != nil { -// return err -// } -// -// return nil -//} - -// addCertificateToStore adds certificate certId to each of the stores configured by stores. Note that stores is a list of -// map[string]interface{} and contains the required configuration for api.AddCertificateToStores(). -func addCertificateToStore(ctx context.Context, conn *api.Client, certificateId int, certificateAlias string, keyPassword string, storeId string) error { - var storesStruct []api.CertificateStore - - storeRequest := new(api.CertificateStore) - - storeRequest.CertificateStoreId = storeId - storeRequest.Alias = certificateAlias - - storeRequest.IncludePrivateKey = true //todo: make this configurable - storeRequest.Overwrite = true - storeRequest.PfxPassword = keyPassword - storesStruct = append(storesStruct, *storeRequest) - - // We want Keyfactor to immediately apply these changes. - tflog.Debug(ctx, "Creating immediate request to add certificate to store") - - schedule := &api.InventorySchedule{ - Immediate: boolToPointer(true), - } - config := &api.AddCertificateToStore{ - CertificateId: certificateId, - CertificateStores: &storesStruct, - InventorySchedule: schedule, - } - tflog.Debug(ctx, fmt.Sprintf("Adding certificate %v to Keyfactor store %v", certificateId, storeId)) - _, err := conn.AddCertificateToStores(config) - if err != nil { - tflog.Error(ctx, fmt.Sprintf("Error adding certificate %v to Keyfactor store %v: %v", certificateId, storeId, err)) - return err - } - tflog.Debug(ctx, fmt.Sprintf("Successfully added certificate %v to Keyfactor store %v", certificateId, storeId)) - return nil -} - -func validateCertificatesInStore(ctx context.Context, conn *api.Client, certificateId int, storeId string, maxIterations int) error { - valid := false - tflog.Debug(ctx, fmt.Sprintf("Validating certificate %v is in Keyfactor store %v", certificateId, storeId)) - retryDelay := 2 - for i := 0; i < maxIterations; i++ { - args := &api.GetCertificateContextArgs{ - IncludeLocations: boolToPointer(true), - Id: certificateId, - } - certificateData, err := conn.GetCertificateContext(args) - if err != nil { - return err - } - - certLocs := certificateData.Locations - for _, loc := range certLocs { - if loc.CertStoreId == storeId { - valid = true - i = maxIterations + 1 //break outer loop - break - } - } - - //if len(findStringDifference(certificateStores, storeList)) == 0 && len(findStringDifference(storeList, certificateStores)) == 0 { - // valid = true - // break - //} - if !valid && i+1 < maxIterations { - retryDelay = retryDelay * (i + 1) - if retryDelay > 30 { - retryDelay = 30 - } - tflog.Debug(ctx, fmt.Sprintf("Certificate %v not found in Keyfactor store %v. Retrying in %v seconds", certificateId, storeId, retryDelay)) - time.Sleep(time.Duration(retryDelay) * time.Second) - } - } - if !valid { - return fmt.Errorf("validateCertificatesInStore timed out. certificate could deploy eventually, but terraform change operation will fail. run terraform plan later to verify that the certificate was deployed successfully") - } - return nil -} - -func removeCertificateAliasFromStore(ctx context.Context, conn *api.Client, certificateStores *[]api.CertificateStore) error { - // We want Keyfactor to immediately apply these changes. - schedule := &api.InventorySchedule{ - Immediate: boolToPointer(true), - } - config := &api.RemoveCertificateFromStore{ - CertificateStores: certificateStores, - InventorySchedule: schedule, - } - - _, err := conn.RemoveCertificateFromStores(config) - if err != nil { - return err - } - - return nil -} diff --git a/keyfactor/resource_keyfactor_certificate_deploy_test.go b/keyfactor/resource_keyfactor_certificate_deploy_test.go deleted file mode 100644 index 507bc3d7..00000000 --- a/keyfactor/resource_keyfactor_certificate_deploy_test.go +++ /dev/null @@ -1,139 +0,0 @@ -package keyfactor - -//import ( -// "fmt" -// "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -// "os" -// "testing" -//) -// -//type certificateDeploymentTestCase struct { -// certificateTestCase -// storeID string -//} -// -//func TestAccKeyfactorCertificateDeploymentResource(t *testing.T) { -// -// r := certificateDeploymentTestCase{ -// certificateTestCase: certificateTestCase{ -// template: os.Getenv("KEYFACTOR_CERTIFICATE_TEMPLATE_NAME"), -// cn: "terraform_test_certificate", -// o: "Keyfactor Inc.", -// l: "Independence", -// c: "US", -// ou: "Integrations Engineering", -// st: "OH", -// ca: fmt.Sprintf(`%s\\%s`, os.Getenv("KEYFACTOR_CERTIFICATE_CA_DOMAIN"), os.Getenv("KEYFACTOR_CERTIFICATE_CA_NAME")), -// ipSans: `["192.168.0.2", "10.10.0.9"]`, -// dnsSans: `["tfprovider.keyfactor.com", "terraform_test_certificate"]`, -// metadata: nil, -// email: "", -// keyPassword: os.Getenv("KEYFACTOR_CERTIFICATE_PASSWORD"), -// resourceName: "keyfactor_certificate_deployment.PFXCertificate", -// }, -// storeID: os.Getenv("KEYFACTOR_CERTIFICATE_STORE_ID"), -// } -// -// // Testing PFX certificate -// resource.Test(t, resource.TestCase{ -// PreCheck: func() { testAccPreCheck(t) }, -// ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, -// Steps: []resource.TestStep{ -// // Create and Read testing -// { -// //ResourceName: "", -// //PreConfig: nil, -// //Taint: nil, -// Config: testAccKeyfactorCertificateDeploymentResourcePFXConfig(r), -// Check: resource.ComposeAggregateTestCheckFunc( -// resource.TestCheckResourceAttrSet(r.resourceName, "id"), -// resource.TestCheckResourceAttrSet(r.resourceName, "id"), -// resource.TestCheckResourceAttrSet(r.resourceName, "id"), -// resource.TestCheckResourceAttrSet(r.resourceName, "id"), -// ), -// //Destroy: false, -// //ExpectNonEmptyPlan: false, -// //ExpectError: nil, -// //PlanOnly: false, -// //PreventDiskCleanup: false, -// //PreventPostDestroyRefresh: false, -// //SkipFunc: nil, -// //ImportState: false, -// //ImportStateId: "", -// //ImportStateIdPrefix: "", -// //ImportStateIdFunc: nil, -// //ImportStateCheck: nil, -// //ImportStateVerify: false, -// //ImportStateVerifyIgnore: nil, -// //ProviderFactories: nil, -// //ProtoV5ProviderFactories: nil, -// //ProtoV6ProviderFactories: nil, -// //ExternalProviders: nil, -// }, -// // ImportState testing -// //{ -// // ResourceName: "scaffolding_example.test", -// // ImportState: false, -// // ImportStateVerify: false, -// // // This is not normally necessary, but is here because this -// // // example code does not have an actual upstream service. -// // // Once the Read method is able to refresh information from -// // // the upstream service, this can be removed. -// // ImportStateVerifyIgnore: []string{"configurable_attribute"}, -// //}, -// // Update and Read testing -// { -// Config: testAccKeyfactorCertificateDeploymentResourcePFXConfig(r), -// Check: resource.ComposeAggregateTestCheckFunc( -// resource.TestCheckResourceAttrSet(r.resourceName, "id"), -// resource.TestCheckResourceAttrSet(r.resourceName, "serial_number"), -// resource.TestCheckResourceAttrSet(r.resourceName, "issuer_dn"), -// resource.TestCheckResourceAttrSet(r.resourceName, "thumbprint"), -// resource.TestCheckResourceAttrSet(r.resourceName, "keyfactor_request_id"), -// resource.TestCheckResourceAttrSet(r.resourceName, "certificate_pem"), -// resource.TestCheckResourceAttrSet(r.resourceName, "certificate_chain"), -// resource.TestCheckResourceAttrSet(r.resourceName, "certificate_authority"), -// resource.TestCheckResourceAttrSet(r.resourceName, "certificate_template"), -// resource.TestCheckResourceAttrSet(r.resourceName, "dns_sans.#"), -// resource.TestCheckResourceAttrSet(r.resourceName, "certificate_authority"), -// resource.TestCheckResourceAttrSet(r.resourceName, "certificate_template"), -// resource.TestCheckResourceAttrSet(r.resourceName, "dns_sans.#"), -// resource.TestCheckResourceAttrSet(r.resourceName, "ip_sans.#"), -// resource.TestCheckResourceAttrSet(r.resourceName, "metadata.%"), -// ), -// }, -// // Delete testing automatically occurs in TestCase -// }, -// }) -//} -// -//func testAccKeyfactorCertificateDeploymentResourcePFXConfig(t certificateDeploymentTestCase) string { -// output := fmt.Sprintf(` -//resource "keyfactor_certificate" "PFXCertificate" { -// subject = { -// subject_common_name = "%s" -// subject_organization = "%s" -// subject_locality = "%s" -// subject_country = "%s" -// subject_organizational_unit = "%s" -// subject_state = "%s" -// } -// -// ip_sans = %s -// dns_sans = %s -// -// key_password = "%s" # Please don't use this password in production pass in an environmental variable or something -// certificate_authority = "%s" -// certificate_template = "%s" -// metadata = { -// "Email-Contact" = "%s" # Note metadata keys must be defined in Keyfactor -// } -//} -//resource "keyfactor_certificate_deployment" "PFXCertificateDeployment" { -// certificate_id = keyfactor_certificate.PFXCertificate.id -// certificate_store_id = "%s" -// certificate_alias = keyfactor_certificate.PFXCertificate.subject.subject_common_name -//} -//`, t.cn, t.o, t.l, t.c, t.ou, t.st, t.ipSans, t.dnsSans, t.keyPassword, t.ca, t.template, t.email, t.storeID) -// return output -//} diff --git a/keyfactor/resource_keyfactor_certificate_store.go b/keyfactor/resource_keyfactor_certificate_store.go deleted file mode 100644 index f1572c5c..00000000 --- a/keyfactor/resource_keyfactor_certificate_store.go +++ /dev/null @@ -1,474 +0,0 @@ -package keyfactor - -import ( - "context" - "fmt" - "github.com/Keyfactor/keyfactor-go-client/api" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" - "log" - "strconv" - "strings" -) - -type resourceCertificateStoreType struct{} - -func (r resourceCertificateStoreType) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfsdk.Schema{ - Attributes: map[string]tfsdk.Attribute{ - "container_id": { - Type: types.Int64Type, - Optional: true, - Description: "Container identifier of the store's associated certificate store container.", - }, - "client_machine": { - Type: types.StringType, - Required: true, - Description: "Client machine name; value depends on certificate store type. See API reference guide", - }, - "store_path": { - Type: types.StringType, - Required: true, - Description: "Path to the new certificate store on a target. Format varies depending on type.", - }, - "store_type": { - Type: types.StringType, - Required: true, - Description: "Short name of certificate store type. See API reference guide", - }, - "approved": { - Type: types.BoolType, - Attributes: nil, - //DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // // For some reason Terraform detects this particular function as having drift; this function - // // gives us a definitive answer. - // return !d.HasChange(k) - //}, - - Description: "Bool that indicates the approval status of store created. Default is true, omit if unsure.", - MarkdownDescription: "", - Required: false, - Optional: true, - Computed: false, - }, - "create_if_missing": { - Type: types.BoolType, - Optional: true, - Description: "Bool that indicates if the store should be created with information provided. Valid only for JKS type, omit if unsure.", - }, - "properties": { - Type: types.MapType{ElemType: types.StringType}, - Optional: true, - Description: "Certificate properties specific to certificate store type configured as key-value pairs.", - }, - "agent_id": { - Type: types.StringType, - Required: true, - Description: "String indicating the Keyfactor Command GUID of the orchestrator for the created store.", - }, - "agent_assigned": { - Type: types.BoolType, - Optional: true, - //DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { - // // For some reason Terraform detects this particular function as having drift; this function - // // gives us a definitive answer. - // return !d.HasChange(k) - //}, - Description: "Bool indicating if there is an orchestrator assigned to the new certificate store.", - }, - "container_name": { - Type: types.StringType, - Optional: true, - Description: "Name of certificate store's associated container, if applicable.", - }, - "inventory_schedule": { - Type: types.StringType, - Optional: true, - Description: "Inventory schedule for new certificate store.", - }, - "set_new_password_allowed": { - Type: types.BoolType, - Optional: true, - Description: "Indicates whether the store password can be changed.", - }, - "password": { - Type: types.StringType, - Optional: true, - Description: "Sets password for certificate store.", - }, - "id": { - Type: types.StringType, - Computed: true, - Description: "Keyfactor certificate store GUID.", - }, - }, - }, nil -} - -func (r resourceCertificateStoreType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourceCertificateStore{ - p: *(p.(*provider)), - }, nil -} - -type resourceCertificateStore struct { - p provider -} - -func (r resourceCertificateStore) Create(ctx context.Context, request tfsdk.CreateResourceRequest, response *tfsdk.CreateResourceResponse) { - if !r.p.configured { - response.Diagnostics.AddError( - "Provider not configured", - "The provider hasn't been configured before apply, likely because it depends on an unknown value from another resource. This leads to weird stuff happening, so we'd prefer if you didn't do that. Thanks!", - ) - return - } - - // Retrieve values from plan - var plan CertificateStore - diags := request.Plan.Get(ctx, &plan) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Generate API request body from plan - - kfClient := r.p.client - - //certificateStoreId := plan.ID.Value - //ctx = tflog.SetField(ctx, "id", certificateStoreId) - tflog.Info(ctx, "Create called on certificate store resource") - - csType, csTypeErr := r.p.client.GetCertificateStoreTypeByName(plan.StoreType.Value) - if csTypeErr != nil { - response.Diagnostics.AddError( - "Invalid certificate store type.", - fmt.Sprintf("Could not retrieve certificate store type '%s' from Keyfactor"+csTypeErr.Error(), plan.StoreType.Value), - ) - return - } - - containerId := int(plan.ContainerID.Value) - var properties map[string]string - if plan.Properties.Elems != nil { - diags = plan.Properties.ElementsAs(ctx, &properties, false) - - } - schedule, err := createInventorySchedule(plan.InventorySchedule.Value) // TODO: Implement inventory schedule - if err != nil { - response.Diagnostics.AddError( - "Invalid inventory schedule.", - fmt.Sprintf("Could not create inventory schedule: %s", err.Error()), - ) - return - } - newStoreArgs := &api.CreateStoreFctArgs{ - ContainerId: &containerId, - ClientMachine: plan.ClientMachine.Value, - StorePath: plan.StorePath.Value, - CertStoreType: csType.StoreType, - Approved: &plan.Approved.Value, - CreateIfMissing: &plan.CreateIfMissing.Value, - Properties: properties, - AgentId: plan.AgentId.Value, - AgentAssigned: &plan.AgentAssigned.Value, - ContainerName: &plan.ContainerName.Value, - InventorySchedule: schedule, - SetNewPasswordAllowed: &plan.SetNewPasswordAllowed.Value, - Password: createPasswordConfig(plan.Password.Value), - } - - createStoreResponse, err := kfClient.CreateStore(newStoreArgs) - if err != nil { - response.Diagnostics.AddError( - "Error creating certificate store", - "Error creating certificate store: %s"+err.Error(), - ) - return - } - - // Set state - var result = CertificateStore{ - ID: types.String{Value: createStoreResponse.Id}, - ContainerID: types.Int64{Value: int64(createStoreResponse.ContainerId)}, - ClientMachine: types.String{Value: createStoreResponse.ClientMachine}, - StorePath: types.String{Value: createStoreResponse.Storepath}, - StoreType: types.String{Value: plan.StoreType.Value}, - Approved: plan.Approved, - CreateIfMissing: plan.CreateIfMissing, - Properties: plan.Properties, - AgentId: types.String{Value: createStoreResponse.AgentId}, - AgentAssigned: plan.AgentAssigned, - ContainerName: plan.ContainerName, - InventorySchedule: plan.InventorySchedule, - SetNewPasswordAllowed: plan.SetNewPasswordAllowed, - Password: plan.Password, - //Certificates: types.List{ElemType: types.Int64Type, Elems: []attr.Value{}}, - } - - diags = response.State.Set(ctx, result) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - -} - -func (r resourceCertificateStore) Read(ctx context.Context, request tfsdk.ReadResourceRequest, response *tfsdk.ReadResourceResponse) { - var state CertificateStore - diags := request.State.Get(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - tflog.Info(ctx, "Read called on certificate store resource") - certificateStoreId := state.ID.Value - - tflog.SetField(ctx, "id", certificateStoreId) - - _, err := r.p.client.GetCertificateStoreByID(certificateStoreId) - if err != nil { - response.Diagnostics.AddError( - "Error reading certificate store", - "Error reading certificate store: %s"+err.Error(), - ) - return - } - - //password := state.Password.Value - //tflog.Trace(ctx, fmt.Sprintf("Password for store %s: %s", certificateStoreId, password)) - - // Set state - diags = response.State.Set(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } -} - -func (r resourceCertificateStore) Update(ctx context.Context, request tfsdk.UpdateResourceRequest, response *tfsdk.UpdateResourceResponse) { - // Get plan values - var plan CertificateStore - diags := request.Plan.Get(ctx, &plan) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Get current state - var state CertificateStore - diags = request.State.Get(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Generate API request body from plan - containerId := int(plan.ContainerID.Value) - csType, csTypeErr := r.p.client.GetCertificateStoreTypeByName(plan.StoreType.Value) - if csTypeErr != nil { - response.Diagnostics.AddError( - "Invalid certificate store type.", - fmt.Sprintf("Could not retrieve certificate store type '%s' from Keyfactor"+csTypeErr.Error(), plan.StoreType.Value), - ) - return - } - schedule, err := createInventorySchedule(plan.InventorySchedule.Value) // TODO: Implement inventory schedule - if err != nil { - response.Diagnostics.AddError( - "Invalid inventory schedule.", - fmt.Sprintf("Could not create inventory schedule: %s", err.Error()), - ) - return - } - updateStoreArgs := &api.UpdateStoreFctArgs{ - Id: state.ID.Value, - CreateStoreFctArgs: api.CreateStoreFctArgs{ - ContainerId: &containerId, - ClientMachine: plan.ClientMachine.Value, - StorePath: plan.StorePath.Value, - CertStoreType: csType.StoreType, - Approved: &plan.Approved.Value, - CreateIfMissing: &plan.CreateIfMissing.Value, - //Properties: map[string]interface{}(plan.Properties.Elems), - AgentId: plan.AgentId.Value, - AgentAssigned: &plan.AgentAssigned.Value, - ContainerName: &plan.ContainerName.Value, - InventorySchedule: schedule, - SetNewPasswordAllowed: &plan.SetNewPasswordAllowed.Value, - //Password: createPasswordConfig(d.Get("password").([]interface{})), - }} - - updateResponse, err := r.p.client.UpdateStore(updateStoreArgs) - if err != nil { - response.Diagnostics.AddError( - "Error updating certificate store", - "Error updating certificate store: %s"+err.Error(), - ) - } - - result := CertificateStore{ - ID: types.String{Value: state.ID.Value}, - ContainerID: types.Int64{Value: int64(updateResponse.ContainerId)}, - //ClientMachine: types.String{Value: updateResponse.ClientMachine}, - ClientMachine: plan.ClientMachine, - StorePath: plan.StorePath, - StoreType: plan.StoreType, - Approved: plan.Approved, - CreateIfMissing: plan.CreateIfMissing, - Properties: plan.Properties, - AgentId: plan.AgentId, - AgentAssigned: plan.AgentAssigned, - ContainerName: plan.ContainerName, - InventorySchedule: plan.InventorySchedule, - SetNewPasswordAllowed: plan.SetNewPasswordAllowed, - Password: plan.Password, - } - - // Set state - diags = response.State.Set(ctx, &result) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } -} - -func (r resourceCertificateStore) Delete(ctx context.Context, request tfsdk.DeleteResourceRequest, response *tfsdk.DeleteResourceResponse) { - var state CertificateStore - diags := request.State.Get(ctx, &state) - kfClient := r.p.client - - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Get order ID from state - certificateStoreId := state.ID.Value - tflog.SetField(ctx, "id", certificateStoreId) - - // Delete order by calling API - log.Println("[INFO] Deleting certificate resource") - - // When Terraform Destroy is called, we want Keyfactor to revoke the certificate. - - tflog.Info(ctx, fmt.Sprintf("Revoking certificate %s in Keyfactor", certificateStoreId)) - - err := kfClient.DeleteCertificateStore(certificateStoreId) - if err != nil { - response.Diagnostics.AddError("Certificate store delete error.", fmt.Sprintf("Could not delete certificate store '%s' on Keyfactor: "+err.Error(), certificateStoreId)) - return - } - - // Remove resource from state - response.State.RemoveResource(ctx) - -} - -func (r resourceCertificateStore) ImportState(ctx context.Context, request tfsdk.ImportResourceStateRequest, response *tfsdk.ImportResourceStateResponse) { - var state CertificateStore - - tflog.Info(ctx, "Read called on certificate store resource") - certificateStoreId := state.ID.Value - - tflog.SetField(ctx, "id", certificateStoreId) - - readResponse, err := r.p.client.GetCertificateStoreByID(certificateStoreId) - if err != nil { - response.Diagnostics.AddError( - "Error reading certificate store", - "Error reading certificate store: %s"+err.Error(), - ) - return - } - - password := state.Password.Value - tflog.Trace(ctx, fmt.Sprintf("Password for store %s: %s", certificateStoreId, password)) - - if err != nil { - response.Diagnostics.AddError( - "Error reading Keyfactor certificate.", - fmt.Sprintf("Could not retrieve certificate '%s' from Keyfactor: "+err.Error(), certificateStoreId), - ) - return - } - - csType, csTypeErr := r.p.client.GetCertificateStoreType(readResponse.CertStoreType) - if csTypeErr != nil { - response.Diagnostics.AddError( - "Error reading Keyfactor certificate.", - fmt.Sprintf("Could not retrieve certificate store type '%s' from Keyfactor: "+err.Error(), readResponse.CertStoreType), - ) - return - } - // Set state - result := CertificateStore{ - ID: types.String{Value: state.ID.Value}, - ContainerID: types.Int64{Value: int64(readResponse.ContainerId)}, - ClientMachine: types.String{Value: readResponse.ClientMachine}, - StorePath: types.String{Value: readResponse.StorePath}, - StoreType: types.String{Value: csType.Name}, - Approved: types.Bool{Value: readResponse.Approved}, - CreateIfMissing: types.Bool{Value: readResponse.CreateIfMissing}, - //Properties: plan.Properties, - AgentId: types.String{Value: readResponse.AgentId}, - AgentAssigned: types.Bool{Value: readResponse.AgentAssigned}, - ContainerName: types.String{Value: readResponse.ContainerName}, - InventorySchedule: types.String{ - Unknown: false, - Null: true, - Value: fmt.Sprintf("%v", readResponse.InventorySchedule), - }, - SetNewPasswordAllowed: types.Bool{Value: readResponse.SetNewPasswordAllowed}, - //Password: plan.Password, - } - diags := response.State.Set(ctx, &result) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } -} - -func createPasswordConfig(p string) *api.StorePasswordConfig { - password := stringToPointer(p) - res := &api.StorePasswordConfig{ - Value: password, - } - - return res -} - -func createInventorySchedule(interval string) (*api.InventorySchedule, error) { - inventorySchedule := &api.InventorySchedule{} - - if interval == "immediate" { - immediate := true - inventorySchedule.Immediate = &immediate - } else { - if strings.HasSuffix(interval, "m") { - minutes, err := strconv.Atoi(interval[:len(interval)-1]) - if err != nil { - return nil, err - } - iv := &api.InventoryInterval{Minutes: minutes} - inventorySchedule.Interval = iv - return inventorySchedule, nil - } - if interval == "daily" { - daily := &api.InventoryDaily{Time: interval} - inventorySchedule.Daily = daily - return inventorySchedule, nil - } - if interval == "exactly_once" { - once := &api.InventoryOnce{Time: interval} - inventorySchedule.ExactlyOnce = once - return inventorySchedule, nil - } - } - - return inventorySchedule, nil -} diff --git a/keyfactor/resource_keyfactor_certificate_store_test.go b/keyfactor/resource_keyfactor_certificate_store_test.go deleted file mode 100644 index 3164148a..00000000 --- a/keyfactor/resource_keyfactor_certificate_store_test.go +++ /dev/null @@ -1,127 +0,0 @@ -package keyfactor - -import ( - "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "os" - "strconv" - "testing" -) - -type certificateStoreTestCase struct { - orchestrator string - storePath string - agentId string - storeType string - schedule string - containerId int - password string - resourceName string -} - -func TestAccKeyfactorCertificateStoreResource(t *testing.T) { - - containerId1, _ := strconv.Atoi(os.Getenv("KEYFACTOR_CERTIFICATE_STORE_CONTAINER_ID1")) - containerId2, _ := strconv.Atoi(os.Getenv("KEYFACTOR_CERTIFICATE_STORE_CONTAINER_ID2")) - - r := certificateStoreTestCase{ - orchestrator: os.Getenv("KEYFACTOR_CERTIFICATE_STORE_CLIENT_MACHINE"), - storePath: os.Getenv("KEYFACTOR_CERTIFICATE_STORE_PATH"), - agentId: os.Getenv("KEYFACTOR_CERTIFICATE_STORE_ORCHESTRATOR_AGENT_ID"), - storeType: os.Getenv("KEYFACTOR_CERTIFICATE_STORE_TYPE"), - containerId: containerId1, - password: os.Getenv("KEYFACTOR_CERTIFICATE_STORE_PASS"), - resourceName: "keyfactor_certificate_store.tf_acc_test", - } - - // Update to multiple certificateStores test - r2 := r - r2.containerId = containerId2 - r2.schedule = "immediate" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - // Create and Read testing - { - //ResourceName: "", - //PreConfig: nil, - //Taint: nil, - Config: testAccKeyfactorCertificateStoreResourceConfig(r), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(r.resourceName, "id"), - resource.TestCheckResourceAttrSet(r.resourceName, "store_path"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r.resourceName, "store_type"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r.resourceName, "client_machine"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r.resourceName, "agent_id"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r.resourceName, "password"), // TODO: Check specific value - ), - //Destroy: false, - //ExpectNonEmptyPlan: false, - //ExpectError: nil, - //PlanOnly: false, - //PreventDiskCleanup: false, - //PreventPostDestroyRefresh: false, - //SkipFunc: nil, - //ImportState: false, - //ImportStateId: "", - //ImportStateIdPrefix: "", - //ImportStateIdFunc: nil, - //ImportStateCheck: nil, - //ImportStateVerify: false, - //ImportStateVerifyIgnore: nil, - //ProviderFactories: nil, - //ProtoV5ProviderFactories: nil, - //ProtoV6ProviderFactories: nil, - //ExternalProviders: nil, - }, - // ImportState testing - //{ - // ResourceName: "scaffolding_example.test", - // ImportState: false, - // ImportStateVerify: false, - // // This is not normally necessary, but is here because this - // // example code does not have an actual upstream service. - // // Once the Read method is able to refresh information from - // // the upstream service, this can be removed. - // ImportStateVerifyIgnore: []string{"configurable_attribute"}, - //}, - // Update and Read testing - { - Config: testAccKeyfactorCertificateStoreResourceConfig(r2), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(r2.resourceName, "id"), - resource.TestCheckResourceAttrSet(r2.resourceName, "store_path"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r2.resourceName, "store_type"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r2.resourceName, "client_machine"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r2.resourceName, "agent_id"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r2.resourceName, "inventory_schedule"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r2.resourceName, "container_id"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r2.resourceName, "password"), // TODO: Check specific value - ), - }, - // Delete testing automatically occurs in TestCase - }, - }) -} - -func testAccKeyfactorCertificateStoreResourceConfig(t certificateStoreTestCase) string { - output := fmt.Sprintf(` -resource "keyfactor_certificate_store" "iis_trusted_roots" { - client_machine = "%s" # Orchestrator client name - store_path = "%s" # Varies based on store type - agent_id = "%s" # Orchestrator GUID - store_type = "%s" # Must exist in KeyFactor - properties = { - # Optional properties based on the store type - UseSSL = true - } - inventory_schedule = "%s" # How often to update the inventory - container_id = %v # ID of the KeyFactor container - password = "%s" - # The password for the certificate store. Note: This is bad practice, use TF_VAR_ instead. -} -`, t.orchestrator, t.storePath, t.agentId, t.storeType, t.schedule, t.containerId, t.password) - return output -} diff --git a/keyfactor/resource_keyfactor_certificate_store_type.go b/keyfactor/resource_keyfactor_certificate_store_type.go deleted file mode 100644 index 493dfc0c..00000000 --- a/keyfactor/resource_keyfactor_certificate_store_type.go +++ /dev/null @@ -1,461 +0,0 @@ -package keyfactor - -// -//import ( -// "context" -// "fmt" -// "github.com/Keyfactor/keyfactor-go-client/api" -// "github.com/hashicorp/terraform-plugin-framework/attr" -// "github.com/hashicorp/terraform-plugin-framework/diag" -// "github.com/hashicorp/terraform-plugin-framework/tfsdk" -// "github.com/hashicorp/terraform-plugin-framework/types" -// "github.com/hashicorp/terraform-plugin-log/tflog" -// "log" -//) -// -//type resourceCertificateStoreType struct{} -// -//func (r resourceCertificateStoreType) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { -// return tfsdk.Schema{ -// Attributes: map[string]tfsdk.Attribute{ -// "container_id": { -// Type: types.Int64Type, -// Optional: true, -// Description: "Container identifier of the store's associated certificate store container.", -// }, -// "client_machine": { -// Type: types.StringType, -// Required: true, -// Description: "Client machine name; value depends on certificate store type. See API reference guide", -// }, -// "store_path": { -// Type: types.StringType, -// Required: true, -// Description: "Path to the new certificate store on a target. Format varies depending on type.", -// }, -// "store_type": { -// Type: types.StringType, -// Required: true, -// Description: "Short name of certificate store type. See API reference guide", -// }, -// "approved": { -// Type: types.BoolType, -// Optional: true, -// //DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { -// // // For some reason Terraform detects this particular function as having drift; this function -// // // gives us a definitive answer. -// // return !d.HasChange(k) -// //}, -// Description: "Bool that indicates the approval status of store created. Default is true, omit if unsure.", -// }, -// "create_if_missing": { -// Type: types.BoolType, -// Optional: true, -// Description: "Bool that indicates if the store should be created with information provided. Valid only for JKS type, omit if unsure.", -// }, -// "properties": { -// Type: types.MapType{ElemType: types.StringType}, -// Optional: true, -// Description: "Certificate properties specific to certificate store type configured as key-value pairs.", -// }, -// "agent_id": { -// Type: types.StringType, -// Required: true, -// Description: "String indicating the Keyfactor Command GUID of the orchestrator for the created store.", -// }, -// "agent_assigned": { -// Type: types.BoolType, -// Optional: true, -// //DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { -// // // For some reason Terraform detects this particular function as having drift; this function -// // // gives us a definitive answer. -// // return !d.HasChange(k) -// //}, -// Description: "Bool indicating if there is an orchestrator assigned to the new certificate store.", -// }, -// "container_name": { -// Type: types.StringType, -// Optional: true, -// Description: "Name of certificate store's associated container, if applicable.", -// }, -// "inventory_schedule": { -// Type: types.StringType, -// Optional: true, -// Description: "Inventory schedule for new certificate store.", -// }, -// "set_new_password_allowed": { -// Type: types.BoolType, -// Optional: true, -// Description: "Indicates whether the store password can be changed.", -// }, -// "password": { -// Type: types.StringType, -// Optional: true, -// Description: "Sets password for certificate store.", -// }, -// "id": { -// Type: types.StringType, -// Computed: true, -// Description: "Keyfactor certificate store GUID.", -// }, -// "certificates": { -// Type: types.ListType{ElemType: types.Int64Type}, -// Computed: true, -// Description: "A list of certificate IDs associated with the certificate store.", -// }, -// }, -// }, nil -//} -// -//func (r resourceCertificateStoreType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { -// return resourceCertificateStore{ -// p: *(p.(*provider)), -// }, nil -//} -// -//type resourceCertificateStore struct { -// p provider -//} -// -//func (r resourceCertificateStore) Create(ctx context.Context, request tfsdk.CreateResourceRequest, response *tfsdk.CreateResourceResponse) { -// if !r.p.configured { -// response.Diagnostics.AddError( -// "Provider not configured", -// "The provider hasn't been configured before apply, likely because it depends on an unknown value from another resource. This leads to weird stuff happening, so we'd prefer if you didn't do that. Thanks!", -// ) -// return -// } -// -// // Retrieve values from plan -// var plan CertificateStore -// diags := request.Plan.Get(ctx, &plan) -// response.Diagnostics.Append(diags...) -// if response.Diagnostics.HasError() { -// return -// } -// -// // Generate API request body from plan -// -// kfClient := r.p.client -// -// //certificateStoreId := plan.ID.Value -// //ctx = tflog.SetField(ctx, "id", certificateStoreId) -// tflog.Info(ctx, "Create called on certificate store resource") -// -// csType, csTypeErr := r.p.client.GetCertStoreTypeByName(plan.StoreType.Value) -// if csTypeErr != nil { -// response.Diagnostics.AddError( -// "Invalid certificate store type.", -// fmt.Sprintf("Could not retrieve certificate store type '%s' from Keyfactor"+csTypeErr.Error(), plan.StoreType.Value), -// ) -// return -// } -// -// containerId := int(plan.ContainerID.Value) -// var properties map[string]string -// if plan.Properties.Elems != nil { -// diags = plan.Properties.ElementsAs(ctx, &properties, false) -// -// } -// newStoreArgs := &api.CreateStoreFctArgs{ -// ContainerId: &containerId, -// ClientMachine: plan.ClientMachine.Value, -// StorePath: plan.StorePath.Value, -// CertStoreType: csType.StoreType, -// Approved: &plan.Approved.Value, -// CreateIfMissing: &plan.CreateIfMissing.Value, -// Properties: properties, -// AgentId: plan.AgentId.Value, -// AgentAssigned: &plan.AgentAssigned.Value, -// ContainerName: &plan.ContainerName.Value, -// //InventorySchedule: createInventorySchedule(plan.InventorySchedule.Value), // TODO: Implement inventory schedule -// SetNewPasswordAllowed: &plan.SetNewPasswordAllowed.Value, -// Password: createPasswordConfig(plan.Password.Value), -// } -// -// createStoreResponse, err := kfClient.CreateStore(newStoreArgs) -// if err != nil { -// response.Diagnostics.AddError( -// "Error creating certificate store", -// "Error creating certificate store: %s"+err.Error(), -// ) -// return -// } -// -// // Set state -// var result = CertificateStore{ -// ID: types.String{Value: createStoreResponse.Id}, -// ContainerID: types.Int64{Value: int64(createStoreResponse.ContainerId)}, -// ClientMachine: types.String{Value: createStoreResponse.ClientMachine}, -// StorePath: types.String{Value: createStoreResponse.Storepath}, -// StoreType: types.String{Value: plan.StoreType.Value}, -// Approved: plan.Approved, -// CreateIfMissing: plan.CreateIfMissing, -// Properties: plan.Properties, -// AgentId: types.String{Value: createStoreResponse.AgentId}, -// AgentAssigned: plan.AgentAssigned, -// ContainerName: plan.ContainerName, -// InventorySchedule: plan.InventorySchedule, -// SetNewPasswordAllowed: plan.SetNewPasswordAllowed, -// Password: plan.Password, -// Certificates: types.List{ElemType: types.Int64Type, Elems: []attr.Value{}}, -// } -// -// diags = response.State.Set(ctx, result) -// response.Diagnostics.Append(diags...) -// if response.Diagnostics.HasError() { -// return -// } -// -//} -// -//func (r resourceCertificateStore) Read(ctx context.Context, request tfsdk.ReadResourceRequest, response *tfsdk.ReadResourceResponse) { -// var state CertificateStore -// diags := request.State.Get(ctx, &state) -// response.Diagnostics.Append(diags...) -// if response.Diagnostics.HasError() { -// return -// } -// -// tflog.Info(ctx, "Read called on certificate store resource") -// certificateStoreId := state.ID.Value -// -// tflog.SetField(ctx, "id", certificateStoreId) -// -// _, err := r.p.client.GetCertificateStoreByID(certificateStoreId) -// if err != nil { -// response.Diagnostics.AddError( -// "Error reading certificate store", -// "Error reading certificate store: %s"+err.Error(), -// ) -// return -// } -// -// password := state.Password.Value -// tflog.Trace(ctx, fmt.Sprintf("Password for store %s: %s", certificateStoreId, password)) -// -// if err != nil { -// response.Diagnostics.AddError( -// "Error reading Keyfactor certificate.", -// fmt.Sprintf("Could not retrieve certificate '%s' from Keyfactor: "+err.Error(), certificateStoreId), -// ) -// return -// } -// -// // Set state -// diags = response.State.Set(ctx, &state) -// response.Diagnostics.Append(diags...) -// if response.Diagnostics.HasError() { -// return -// } -//} -// -//func (r resourceCertificateStore) Update(ctx context.Context, request tfsdk.UpdateResourceRequest, response *tfsdk.UpdateResourceResponse) { -// // Get plan values -// var plan CertificateStore -// diags := request.Plan.Get(ctx, &plan) -// response.Diagnostics.Append(diags...) -// if response.Diagnostics.HasError() { -// return -// } -// -// // Get current state -// var state CertificateStore -// diags = request.State.Get(ctx, &state) -// response.Diagnostics.Append(diags...) -// if response.Diagnostics.HasError() { -// return -// } -// -// // Generate API request body from plan -// containerId := int(plan.ContainerID.Value) -// csType, csTypeErr := r.p.client.GetCertStoreTypeByName(plan.StoreType.Value) -// if csTypeErr != nil { -// response.Diagnostics.AddError( -// "Invalid certificate store type.", -// fmt.Sprintf("Could not retrieve certificate store type '%s' from Keyfactor"+csTypeErr.Error(), plan.StoreType.Value), -// ) -// return -// } -// updateStoreArgs := &api.UpdateStoreFctArgs{ -// Id: state.ID.Value, -// CreateStoreFctArgs: api.CreateStoreFctArgs{ -// ContainerId: &containerId, -// ClientMachine: plan.ClientMachine.Value, -// StorePath: plan.StorePath.Value, -// CertStoreType: csType.StoreType, -// Approved: &plan.Approved.Value, -// CreateIfMissing: &plan.CreateIfMissing.Value, -// //Properties: map[string]interface{}(plan.Properties.Elems), -// AgentId: plan.AgentId.Value, -// AgentAssigned: &plan.AgentAssigned.Value, -// ContainerName: &plan.ContainerName.Value, -// //InventorySchedule: createInventorySchedule(d.Get("inventory_schedule").([]interface{})), -// SetNewPasswordAllowed: &plan.SetNewPasswordAllowed.Value, -// //Password: createPasswordConfig(d.Get("password").([]interface{})), -// }} -// -// updateResponse, err := r.p.client.UpdateStore(updateStoreArgs) -// if err != nil { -// response.Diagnostics.AddError( -// "Error updating certificate store", -// "Error updating certificate store: %s"+err.Error(), -// ) -// } -// -// result := CertificateStore{ -// ID: types.String{Value: state.ID.Value}, -// ContainerID: types.Int64{Value: int64(updateResponse.ContainerId)}, -// ClientMachine: types.String{Value: updateResponse.ClientMachine}, -// StorePath: types.String{Value: updateResponse.Storepath}, -// StoreType: plan.StoreType, -// Approved: types.Bool{Value: updateResponse.Approved}, -// CreateIfMissing: types.Bool{Value: updateResponse.CreateIfMissing}, -// Properties: plan.Properties, -// AgentId: types.String{Value: updateResponse.AgentId}, -// AgentAssigned: types.Bool{Value: updateResponse.AgentAssigned}, -// ContainerName: types.String{Value: updateResponse.ContainerName}, -// InventorySchedule: plan.InventorySchedule, -// SetNewPasswordAllowed: types.Bool{Value: updateResponse.SetNewPasswordAllowed}, -// Password: plan.Password, -// } -// -// // Set state -// diags = response.State.Set(ctx, &result) -// response.Diagnostics.Append(diags...) -// if response.Diagnostics.HasError() { -// return -// } -//} -// -//func (r resourceCertificateStore) Delete(ctx context.Context, request tfsdk.DeleteResourceRequest, response *tfsdk.DeleteResourceResponse) { -// var state CertificateStore -// diags := request.State.Get(ctx, &state) -// kfClient := r.p.client -// -// response.Diagnostics.Append(diags...) -// if response.Diagnostics.HasError() { -// return -// } -// -// // Get order ID from state -// certificateStoreId := state.ID.Value -// tflog.SetField(ctx, "id", certificateStoreId) -// -// // Delete order by calling API -// log.Println("[INFO] Deleting certificate resource") -// -// // When Terraform Destroy is called, we want Keyfactor to revoke the certificate. -// -// tflog.Info(ctx, fmt.Sprintf("Revoking certificate %s in Keyfactor", certificateStoreId)) -// -// err := kfClient.DeleteCertificateStore(certificateStoreId) -// if err != nil { -// response.Diagnostics.AddError("Certificate store delete error.", fmt.Sprintf("Could not delete certificate store '%s' on Keyfactor: "+err.Error(), certificateStoreId)) -// return -// } -// -// // Remove resource from state -// response.State.RemoveResource(ctx) -// -//} -// -//func (r resourceCertificateStore) ImportState(ctx context.Context, request tfsdk.ImportResourceStateRequest, response *tfsdk.ImportResourceStateResponse) { -// var state CertificateStore -// -// tflog.Info(ctx, "Read called on certificate store resource") -// certificateStoreId := state.ID.Value -// -// tflog.SetField(ctx, "id", certificateStoreId) -// -// readResponse, err := r.p.client.GetCertificateStoreByID(certificateStoreId) -// if err != nil { -// response.Diagnostics.AddError( -// "Error reading certificate store", -// "Error reading certificate store: %s"+err.Error(), -// ) -// return -// } -// -// password := state.Password.Value -// tflog.Trace(ctx, fmt.Sprintf("Password for store %s: %s", certificateStoreId, password)) -// -// if err != nil { -// response.Diagnostics.AddError( -// "Error reading Keyfactor certificate.", -// fmt.Sprintf("Could not retrieve certificate '%s' from Keyfactor: "+err.Error(), certificateStoreId), -// ) -// return -// } -// -// csType, csTypeErr := r.p.client.GetCertStoreType(readResponse.CertStoreType) -// if csTypeErr != nil { -// response.Diagnostics.AddError( -// "Error reading Keyfactor certificate.", -// fmt.Sprintf("Could not retrieve certificate store type '%s' from Keyfactor: "+err.Error(), readResponse.CertStoreType), -// ) -// return -// } -// // Set state -// result := CertificateStore{ -// ID: types.String{Value: state.ID.Value}, -// ContainerID: types.Int64{Value: int64(readResponse.ContainerId)}, -// ClientMachine: types.String{Value: readResponse.ClientMachine}, -// StorePath: types.String{Value: readResponse.StorePath}, -// StoreType: types.String{Value: csType.Name}, -// Approved: types.Bool{Value: readResponse.Approved}, -// CreateIfMissing: types.Bool{Value: readResponse.CreateIfMissing}, -// //Properties: plan.Properties, -// AgentId: types.String{Value: readResponse.AgentId}, -// AgentAssigned: types.Bool{Value: readResponse.AgentAssigned}, -// ContainerName: types.String{Value: readResponse.ContainerName}, -// //InventorySchedule: plan.InventorySchedule, -// SetNewPasswordAllowed: types.Bool{Value: readResponse.SetNewPasswordAllowed}, -// //Password: plan.Password, -// } -// diags := response.State.Set(ctx, &result) -// response.Diagnostics.Append(diags...) -// if response.Diagnostics.HasError() { -// return -// } -//} -// -//func createPasswordConfig(p string) *api.StorePasswordConfig { -// password := stringToPointer(p) -// res := &api.StorePasswordConfig{ -// Value: password, -// } -// -// return res -//} -// -////func createInventorySchedule(interval string) (*api.InventorySchedule, error) { -//// inventorySchedule := &api.InventorySchedule{} -//// -//// if interval == "immediate" { -//// immediate := true -//// inventorySchedule.Immediate = &immediate -//// } else { -//// if strings.HasSuffix(interval, "m") { -//// minutes, err := strconv.Atoi(interval[:len(interval)-1]) -//// if err != nil { -//// return nil, err -//// } -//// iv := &api.InventoryInterval{Minutes: minutes} -//// inventorySchedule.Interval = iv -//// return inventorySchedule, nil -//// } -//// if key == "daily" { -//// daily := &api.InventoryDaily{Time: innerValue.(string)} -//// inventorySchedule.Daily = daily -//// return inventorySchedule -//// } -//// if key == "exactly_once" { -//// once := &api.InventoryOnce{Time: innerValue.(string)} -//// inventorySchedule.ExactlyOnce = once -//// return inventorySchedule -//// } -//// } -//// -//// return inventorySchedule -////} diff --git a/keyfactor/resource_keyfactor_certificate_test.go b/keyfactor/resource_keyfactor_certificate_test.go deleted file mode 100644 index 402ce96d..00000000 --- a/keyfactor/resource_keyfactor_certificate_test.go +++ /dev/null @@ -1,381 +0,0 @@ -package keyfactor - -import ( - "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "os" - "testing" -) - -type certificateTestCase struct { - template string - cn string - o string - l string - c string - ou string - st string - email string - ipSans string - dnsSans string - metadata map[string]string - keyPassword string - ca string - resourceName string -} - -const CsrContent = `-----BEGIN CERTIFICATE REQUEST-----\nMIIFMTCCAxkCAQAwgesxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJPSDEVMBMGA1UE\nBxMMSW5kZXBlbmRlbmNlMUcwEAYDVQQJEwlTdWl0ZSAyMDAwEwYDVQQJEwxTZWNv\nbmQgRmxvb3IwHgYDVQQJExc2MTUwIE9hayBUcmVlIEJvdWxldmFyZDEOMAwGA1UE\nERMFNDQxMzExFzAVBgNVBAoTDktleWZhY3RvciBJbmMuMSEwHwYDVQQLExhJbnRl\nZ3JhdGlvbnMgRW5naW5lZXJpbmcxIzAhBgNVBAMMGnRlcnJhZm9ybV90ZXN0X2Nl\ncnRpZmljYXRlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAy4sTj1k2\n7rabAXphqKaA/vpr61BEDdVQ/7J2nx3riSDqZZjyCKAjXGLqWsJGvBb9hbfnhH7J\nw83QwZTJab89BAYGTnHE4KB7eBFleI0aEvI09CPaNnjoiFYXc6s/Yhgv8FNUnlbR\nvkaEbKW4A4Mz83b2fNCHfJY5NnE6jr/gMmYnDjXh50yBAR4HS3t7GPZLsar39xpG\ngnKlCC8LGDRJ8CcMilkvH2bNLTo0nsckTJV9ttuDsmWLd9rANu843Va8XZzmq9ej\noWLn65MQEhqAObD5sZPNnQkH8c+5IGL+fQJW3y+nqe4zu+9L8nNEgXa6ANNJRIwy\n+Mug7+0IWlLJf5EnIB1z2stJqWFf3kVaEO1BakN8Qkv1tugpKazVKl6rs2CC97Ww\nQgXpD4tvOyCZxHs+Ok3SK183Q+GkM7WjLuBP9ainY4nJ76SbTOwPw8JVQB+4EkDo\naff1X2zctcmK1/Ri5kyMGqIQw4vQ+YZKzNJIJokNNn5K+u6ppOfxswOp0bZ4fG/M\nc1BKjAHBGDE10GaLlYFBR6/HTwLHDF5t1LpdhqzqLx8OpsaSJCN3xRUTlu5TsZa+\nn5NEgJS9bDHgqjv1dF68loZ3ILu8pebznh6vV+q3Jc8b8HIMXJ+hEoKZ1ldBgSeB\nCzHSDwVbS9L8swwzAAP54I/RDQR83pM1xH8CAwEAAaAAMA0GCSqGSIb3DQEBCwUA\nA4ICAQCV3Zw86hug66jloFFks1D0pGT7StuSkIFeYm46i0jEorVuhc4MqKYb/4C3\nVh0TnYHaNqfqlYJRHln2909tk4FMlQss8w/RxhCrSzJpr5px1XOWNKIJVnEjQAXS\n4O5//pe/qOwK1jH8J8RMEEZLdfFyWpJtav9Js+xK7lH/aXCbxExxYPDRuZCTiH9S\n6rxCIGmKkq2wtm36Tw3UsPLHp6IFdGag3WiD/ye4OpIT+6Tl0AX1qC3GV2S46/jv\ndPtr1EXFIgFX6mRzlA6/J3QgTaxBhxFITaS6dyCHUlSgEcbaVJ0rWre9zfQ38VEa\nUwpLU58Bx1ysVF7goQxYQxnHz2lVClA9WCCZt1NU3IX+QLqk1WU5idu8AfmvZXNI\nhrhcF/PCvH9eAfsqwECt/VsY3ferRtrCEves2UX7r/c4s0L/ZvYS7X9w3MxaJikc\nsewMB3Sj5xVc5XR71C6we16RrpEZ/bTtl8MPSY3b+pPf6YAqQlaziM2swdoQrp1c\n1DQElo1YlICF2gPQH9tJZcgDclw1W+1o77q34hIwktTtKDcVIs4WYTNwo8fn1Xtn\n7fU9cUBMepaIgZQfSz9KpLWG+GwbEgCtahLOpH5FNv+2e8dP0VZeWBCCAkav27oh\nxwK1aZ8hvc2E//sbJT0Swx8hIhyS+EYKpg1DzEZbwBmRch8C/g==\n-----END CERTIFICATE REQUEST-----\n` - -func TestAccKeyfactorCertificateResource(t *testing.T) { - - r := certificateTestCase{ - template: os.Getenv("KEYFACTOR_CERTIFICATE_TEMPLATE_NAME"), - cn: "terraform_test_certificate", - o: "Keyfactor Inc.", - l: "Independence", - c: "US", - ou: "Integrations Engineering", - st: "OH", - ca: fmt.Sprintf(`%s\\%s`, os.Getenv("KEYFACTOR_CERTIFICATE_CA_DOMAIN"), os.Getenv("KEYFACTOR_CERTIFICATE_CA_NAME")), - ipSans: `["192.168.0.2", "10.10.0.9"]`, - dnsSans: `["tfprovider.keyfactor.com", "terraform_test_certificate"]`, - metadata: nil, - email: "", - keyPassword: os.Getenv("KEYFACTOR_CERTIFICATE_PASSWORD"), - resourceName: "keyfactor_certificate.PFXCertificate", - } - - r3 := certificateTestCase{ - template: os.Getenv("KEYFACTOR_CERTIFICATE_TEMPLATE_NAME"), - cn: "terraform_test_certificate", - o: "", - l: "", - c: "", - ou: "", - st: "", - ca: fmt.Sprintf(`%s\\%s`, os.Getenv("KEYFACTOR_CERTIFICATE_CA_DOMAIN"), os.Getenv("KEYFACTOR_CERTIFICATE_CA_NAME")), - //ipSans: `["192.168.0.2", "10.10.0.9"]`, - //dnsSans: `["tfprovider.keyfactor.com", "terraform_test_certificate"]`, - metadata: nil, - email: "", - keyPassword: os.Getenv("KEYFACTOR_CERTIFICATE_PASSWORD"), - resourceName: "keyfactor_certificate.PFXCertificate", - } - - r2 := r - r2.email = "kfadmin@keyfactor.com" - - // Testing PFX certificate - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - // Create and Read testing - { - //ResourceName: "", - //PreConfig: nil, - //Taint: nil, - Config: testAccKeyfactorCertificateResourcePFXConfig(r), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(r.resourceName, "id"), - resource.TestCheckResourceAttrSet(r.resourceName, "serial_number"), - resource.TestCheckResourceAttrSet(r.resourceName, "issuer_dn"), - resource.TestCheckResourceAttrSet(r.resourceName, "thumbprint"), - resource.TestCheckResourceAttrSet(r.resourceName, "keyfactor_request_id"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_pem"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_chain"), - resource.TestCheckResourceAttrSet(r.resourceName, "private_key"), - - resource.TestCheckResourceAttrSet(r.resourceName, "subject.%"), - resource.TestCheckResourceAttrSet(r.resourceName, "subject.subject_common_name"), - resource.TestCheckResourceAttrSet(r.resourceName, "subject.subject_locality"), - resource.TestCheckResourceAttrSet(r.resourceName, "subject.subject_organization"), - resource.TestCheckResourceAttrSet(r.resourceName, "subject.subject_state"), - resource.TestCheckResourceAttrSet(r.resourceName, "subject.subject_country"), - resource.TestCheckResourceAttrSet(r.resourceName, "subject.subject_organizational_unit"), - resource.TestCheckResourceAttrSet(r.resourceName, "key_password"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_authority"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_template"), - resource.TestCheckResourceAttrSet(r.resourceName, "dns_sans.#"), - resource.TestCheckResourceAttrSet(r.resourceName, "ip_sans.#"), - resource.TestCheckResourceAttrSet(r.resourceName, "metadata.%"), - ), - //Destroy: false, - //ExpectNonEmptyPlan: false, - //ExpectError: nil, - //PlanOnly: false, - //PreventDiskCleanup: false, - //PreventPostDestroyRefresh: false, - //SkipFunc: nil, - //ImportState: false, - //ImportStateId: "", - //ImportStateIdPrefix: "", - //ImportStateIdFunc: nil, - //ImportStateCheck: nil, - //ImportStateVerify: false, - //ImportStateVerifyIgnore: nil, - //ProviderFactories: nil, - //ProtoV5ProviderFactories: nil, - //ProtoV6ProviderFactories: nil, - //ExternalProviders: nil, - }, - // ImportState testing - //{ - // ResourceName: "scaffolding_example.test", - // ImportState: false, - // ImportStateVerify: false, - // // This is not normally necessary, but is here because this - // // example code does not have an actual upstream service. - // // Once the Read method is able to refresh information from - // // the upstream service, this can be removed. - // ImportStateVerifyIgnore: []string{"configurable_attribute"}, - //}, - // Update and Read testing - { - Config: testAccKeyfactorCertificateResourcePFXConfig(r2), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(r.resourceName, "id"), - resource.TestCheckResourceAttrSet(r.resourceName, "serial_number"), - resource.TestCheckResourceAttrSet(r.resourceName, "issuer_dn"), - resource.TestCheckResourceAttrSet(r.resourceName, "thumbprint"), - resource.TestCheckResourceAttrSet(r.resourceName, "keyfactor_request_id"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_pem"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_chain"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_authority"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_template"), - resource.TestCheckResourceAttrSet(r.resourceName, "dns_sans.#"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_authority"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_template"), - resource.TestCheckResourceAttrSet(r.resourceName, "dns_sans.#"), - resource.TestCheckResourceAttrSet(r.resourceName, "ip_sans.#"), - resource.TestCheckResourceAttrSet(r.resourceName, "metadata.%"), - ), - }, - // Delete testing automatically occurs in TestCase - }, - }) - // Testing PFX w/ min subject - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - // Create and Read testing - { - //ResourceName: "", - //PreConfig: nil, - //Taint: nil, - Config: testAccKeyfactorCertificateResourcePFXConfig2(r3), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(r.resourceName, "id"), - resource.TestCheckResourceAttrSet(r.resourceName, "serial_number"), - resource.TestCheckResourceAttrSet(r.resourceName, "issuer_dn"), - resource.TestCheckResourceAttrSet(r.resourceName, "thumbprint"), - resource.TestCheckResourceAttrSet(r.resourceName, "keyfactor_request_id"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_pem"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_chain"), - resource.TestCheckResourceAttrSet(r.resourceName, "private_key"), - - resource.TestCheckResourceAttrSet(r.resourceName, "subject.%"), - resource.TestCheckResourceAttrSet(r.resourceName, "subject.subject_common_name"), - //resource.TestCheckResourceAttrSet(r.resourceName, "subject.subject_locality"), - //resource.TestCheckResourceAttrSet(r.resourceName, "subject.subject_organization"), - //resource.TestCheckResourceAttrSet(r.resourceName, "subject.subject_state"), - //resource.TestCheckResourceAttrSet(r.resourceName, "subject.subject_country"), - //resource.TestCheckResourceAttrSet(r.resourceName, "subject.subject_organizational_unit"), - resource.TestCheckResourceAttrSet(r.resourceName, "key_password"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_authority"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_template"), - //resource.TestCheckResourceAttrSet(r.resourceName, "dns_sans.#"), - //resource.TestCheckResourceAttrSet(r.resourceName, "ip_sans.#"), - resource.TestCheckResourceAttrSet(r.resourceName, "metadata.%"), - ), - //Destroy: false, - //ExpectNonEmptyPlan: false, - //ExpectError: nil, - //PlanOnly: false, - //PreventDiskCleanup: false, - //PreventPostDestroyRefresh: false, - //SkipFunc: nil, - //ImportState: false, - //ImportStateId: "", - //ImportStateIdPrefix: "", - //ImportStateIdFunc: nil, - //ImportStateCheck: nil, - //ImportStateVerify: false, - //ImportStateVerifyIgnore: nil, - //ProviderFactories: nil, - //ProtoV5ProviderFactories: nil, - //ProtoV6ProviderFactories: nil, - //ExternalProviders: nil, - }, - // ImportState testing - //{ - // ResourceName: "scaffolding_example.test", - // ImportState: false, - // ImportStateVerify: false, - // // This is not normally necessary, but is here because this - // // example code does not have an actual upstream service. - // // Once the Read method is able to refresh information from - // // the upstream service, this can be removed. - // ImportStateVerifyIgnore: []string{"configurable_attribute"}, - //}, - // Update and Read testing - { - Config: testAccKeyfactorCertificateResourcePFXConfig(r2), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(r.resourceName, "id"), - resource.TestCheckResourceAttrSet(r.resourceName, "serial_number"), - resource.TestCheckResourceAttrSet(r.resourceName, "issuer_dn"), - resource.TestCheckResourceAttrSet(r.resourceName, "thumbprint"), - resource.TestCheckResourceAttrSet(r.resourceName, "keyfactor_request_id"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_pem"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_chain"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_authority"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_template"), - resource.TestCheckResourceAttrSet(r.resourceName, "dns_sans.#"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_authority"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_template"), - resource.TestCheckResourceAttrSet(r.resourceName, "dns_sans.#"), - resource.TestCheckResourceAttrSet(r.resourceName, "ip_sans.#"), - resource.TestCheckResourceAttrSet(r.resourceName, "metadata.%"), - ), - }, - // Delete testing automatically occurs in TestCase - }, - }) - // Testing CSR certificate - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - // Create and Read testing - { - //ResourceName: "", - //PreConfig: nil, - //Taint: nil, - Config: testAccKeyfactorCertificateResourceCSRConfig(r), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(r.resourceName, "id"), - resource.TestCheckResourceAttrSet(r.resourceName, "csr"), - //resource.TestCheckResourceAttr(r.resourceName, "csr", CsrContent), - resource.TestCheckResourceAttrSet(r.resourceName, "serial_number"), - resource.TestCheckResourceAttrSet(r.resourceName, "issuer_dn"), - resource.TestCheckResourceAttrSet(r.resourceName, "thumbprint"), - resource.TestCheckResourceAttrSet(r.resourceName, "keyfactor_request_id"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_pem"), - resource.TestCheckResourceAttrSet(r.resourceName, "certificate_chain"), - ), - //Destroy: false, - //ExpectNonEmptyPlan: false, - //ExpectError: nil, - //PlanOnly: false, - //PreventDiskCleanup: false, - //PreventPostDestroyRefresh: false, - //SkipFunc: nil, - //ImportState: false, - //ImportStateId: "", - //ImportStateIdPrefix: "", - //ImportStateIdFunc: nil, - //ImportStateCheck: nil, - //ImportStateVerify: false, - //ImportStateVerifyIgnore: nil, - //ProviderFactories: nil, - //ProtoV5ProviderFactories: nil, - //ProtoV6ProviderFactories: nil, - //ExternalProviders: nil, - }, - // ImportState testing - //{ - // ResourceName: "scaffolding_example.test", - // ImportState: false, - // ImportStateVerify: false, - // // This is not normally necessary, but is here because this - // // example code does not have an actual upstream service. - // // Once the Read method is able to refresh information from - // // the upstream service, this can be removed. - // ImportStateVerifyIgnore: []string{"configurable_attribute"}, - //}, - // Update and Read testing - { - Config: testAccKeyfactorCertificateResourcePFXConfig(r2), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(r.resourceName, "metadata.%"), - ), - }, - // Delete testing automatically occurs in TestCase - }, - }) -} - -func testAccKeyfactorCertificateResourcePFXConfig(t certificateTestCase) string { - output := fmt.Sprintf(` -resource "keyfactor_certificate" "PFXCertificate" { - subject = { - subject_common_name = "%s" - subject_organization = "%s" - subject_locality = "%s" - subject_country = "%s" - subject_organizational_unit = "%s" - subject_state = "%s" - } - - ip_sans = %s - dns_sans = %s - - key_password = "%s" # Please don't use this password in production pass in an environmental variable or something - certificate_authority = "%s" - certificate_template = "%s" - metadata = { - "Email-Contact" = "%s" # Note metadata keys must be defined in Keyfactor - } -} - -`, t.cn, t.o, t.l, t.c, t.ou, t.st, t.ipSans, t.dnsSans, t.keyPassword, t.ca, t.template, t.email) - return output -} - -func testAccKeyfactorCertificateResourcePFXConfig2(t certificateTestCase) string { - output := fmt.Sprintf(` -resource "keyfactor_certificate" "PFXCertificate" { - subject = { - subject_common_name = "%s" - //subject_organization = "%s" - //subject_locality = "%s" - //subject_country = "%s" - //subject_organizational_unit = "%s" - //subject_state = "%s" - } - - //ip_sans = %s - //dns_sans = %s - - key_password = "%s" # Please don't use this password in production pass in an environmental variable or something - certificate_authority = "%s" - certificate_template = "%s" - metadata = { - "Email-Contact" = "%s" # Note metadata keys must be defined in Keyfactor - } -} - -`, t.cn, t.o, t.l, t.c, t.ou, t.st, t.ipSans, t.dnsSans, t.keyPassword, t.ca, t.template, t.email) - return output -} -func testAccKeyfactorCertificateResourceCSRConfig(t certificateTestCase) string { - output := fmt.Sprintf(` -resource "keyfactor_certificate" "PFXCertificate" { - csr = "%s" - - ip_sans = %s - dns_sans = %s - - key_password = "%s" # Please don't use this password in production pass in an environmental variable or something - certificate_authority = "%s" - certificate_template = "%s" - metadata = { - "Email-Contact" = "%s" # Note metadata keys must be defined in Keyfactor - } -} - -`, CsrContent, t.ipSans, t.dnsSans, t.keyPassword, t.ca, t.template, t.email) - return output -} diff --git a/keyfactor/resource_keyfactor_security_identity.go b/keyfactor/resource_keyfactor_security_identity.go deleted file mode 100644 index d320ea56..00000000 --- a/keyfactor/resource_keyfactor_security_identity.go +++ /dev/null @@ -1,515 +0,0 @@ -package keyfactor - -import ( - "context" - "fmt" - "github.com/Keyfactor/keyfactor-go-client/api" - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" - "log" - "regexp" - "strconv" - "strings" -) - -type resourceSecurityIdentityType struct{} - -func (r resourceSecurityIdentityType) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfsdk.Schema{ - Attributes: map[string]tfsdk.Attribute{ - "account_name": { - Type: types.StringType, - Required: true, - Description: "A string containing the account name for the security identity. For Active Directory user and groups, this will be in the form DOMAIN\\\\user or group name", - PlanModifiers: []tfsdk.AttributePlanModifier{ - tfsdk.RequiresReplace(), - }, - }, - "roles": { - Type: types.ListType{ - ElemType: types.StringType, - }, - Optional: true, - Description: "An array containing the role IDs that the identity is attached to.", - }, - "id": { - Type: types.Int64Type, - Computed: true, - Description: "An integer containing the Keyfactor Command identifier for the security identity.", - }, - "identity_type": { - Type: types.StringType, - Computed: true, - Description: "A string indicating the type of identity—User or Group.", - }, - "valid": { - Type: types.BoolType, - Computed: true, - Description: "A Boolean that indicates whether the security identity's audit XML is valid (true) or not (false). A security identity may become invalid if Keyfactor Command determines that it appears to have been tampered with.", - }, - }, - }, nil -} - -// New resource instance -func (r resourceSecurityIdentityType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourceSecurityIdentity{ - p: *(p.(*provider)), - }, nil -} - -type resourceSecurityIdentity struct { - p provider -} - -func (r resourceSecurityIdentity) Read(ctx context.Context, request tfsdk.ReadResourceRequest, response *tfsdk.ReadResourceResponse) { - var state SecurityIdentity - diags := request.State.Get(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - tflog.Info(ctx, "Read called on security identity resource") - identityId := state.ID.Value - accountName := state.AccountName.Value - tflog.SetField(ctx, "id", identityId) - - identities, err := r.p.client.GetSecurityIdentities() - - if err != nil { - response.Diagnostics.AddError("Error listing identities from Keyfactor.", "Error reading identities: "+err.Error()) - } - - for _, identity := range identities { - //if int64(identity.Id) == identityId { - // tflog.Info(ctx, fmt.Sprintf("Found identity with id: %s", identityId)) - // break - //} - if accountName == identity.AccountName { - tflog.Info(ctx, fmt.Sprintf("Found identity with account name: %s", accountName)) - - var validRoles []attr.Value - var validRolesInterface []interface{} - for _, role := range state.Roles.Elems { - //validRoles = append(validRoles.Elems, role.Name.Value) - tflog.Info(ctx, fmt.Sprintf("Adding role: %s", role)) - tflog.Debug(ctx, fmt.Sprintf("Looking up role %v in Keyfactor", role)) - - //TODO: Verify role exists in Keyfactor or throw warning - re, _ := regexp.Compile(`[^\w]`) - roleStr := re.ReplaceAllString(role.String(), "") - kfRole, roleLookupErr := r.p.client.GetSecurityRole(roleStr) - if roleLookupErr != nil || kfRole == nil { - tflog.Warn(ctx, fmt.Sprintf("Error looking up role %s on Keyfactor.", role)) - response.Diagnostics.AddWarning( - "Error looking up role on Keyfactor.", - fmt.Sprintf("Error looking up role '%s' on Keyfactor. '%s' will not have role '%s'.", roleStr, state.AccountName.Value, roleStr), - ) - continue - } - validRoles = append(validRoles, types.String{Value: fmt.Sprintf("%s", roleStr)}) - validRolesInterface = append(validRolesInterface, kfRole.Id) - } - - state = SecurityIdentity{ - ID: types.Int64{Value: int64(identity.Id)}, - AccountName: types.String{Value: identity.AccountName}, - IdentityType: types.String{Value: identity.IdentityType}, - Roles: types.List{Elems: validRoles, ElemType: types.StringType}, - Valid: types.Bool{Value: identity.Valid}, - } - break - } - - } - - diags = response.State.Set(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } -} - -func (r resourceSecurityIdentity) Update(ctx context.Context, request tfsdk.UpdateResourceRequest, response *tfsdk.UpdateResourceResponse) { - // Get plan values - var plan SecurityIdentity - diags := request.Plan.Get(ctx, &plan) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - //kfClient := r.p.client - tflog.Info(ctx, "Update called on security identity resource") - - // Get current state - var state SecurityIdentity - diags = request.State.Get(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Generate API request body from plan - //var validRoles types.List - var validRoles []attr.Value - var validRolesInterface []interface{} - for _, role := range plan.Roles.Elems { - //validRoles = append(validRoles.Elems, role.Name.Value) - tflog.Info(ctx, fmt.Sprintf("Adding role: %s", role)) - tflog.Debug(ctx, fmt.Sprintf("Looking up role %v in Keyfactor", role)) - - //TODO: Verify role exists in Keyfactor or throw warning - re, err := regexp.Compile(`[^\w]`) - if err != nil { - log.Fatal(err) - } - roleStr := re.ReplaceAllString(role.String(), "") - tflog.Debug(ctx, fmt.Sprintf("Role string: %s", roleStr)) - kfRole, roleLookupErr := r.p.client.GetSecurityRole(roleStr) - if roleLookupErr != nil || kfRole == nil { - tflog.Warn(ctx, fmt.Sprintf("Error looking up role %s on Keyfactor.", role)) - response.Diagnostics.AddWarning( - "Error looking up role on Keyfactor.", - fmt.Sprintf("Error looking up role '%s' on Keyfactor. '%s' will not have role '%s'.", roleStr, state.AccountName.Value, roleStr), - ) - continue - } - validRoles = append(validRoles, types.String{Value: fmt.Sprintf("%s", roleStr)}) - validRolesInterface = append(validRolesInterface, kfRole.Id) - } - - //Update role identities - err := setIdentityRole(ctx, r.p.client, state.AccountName.Value, validRolesInterface) - if err != nil { - response.Diagnostics.AddError("Error updating identity roles.", "Error updating identity roles: "+err.Error()) - } - - var result = SecurityIdentity{ - ID: types.Int64{Value: int64(state.ID.Value)}, - AccountName: types.String{Value: state.AccountName.Value}, - IdentityType: types.String{Value: state.IdentityType.Value}, - Valid: types.Bool{Value: state.Valid.Value}, - Roles: plan.Roles, - } - - // Set state - diags = response.State.Set(ctx, result) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } -} - -func (r resourceSecurityIdentity) Delete(ctx context.Context, request tfsdk.DeleteResourceRequest, response *tfsdk.DeleteResourceResponse) { - var state SecurityIdentity - diags := request.State.Get(ctx, &state) - kfClient := r.p.client - - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Get order ID from state - identityId := state.ID.Value - - // Delete order by calling API - err := kfClient.DeleteSecurityIdentity(int(identityId)) - if err != nil { - response.Diagnostics.AddError( - "Error deleting security identity.", - "Could not delete "+state.AccountName.Value+" from Keyfactor: "+err.Error(), - ) - return - } - - // Remove resource from state - response.State.RemoveResource(ctx) - -} - -func (r resourceSecurityIdentity) Create(ctx context.Context, request tfsdk.CreateResourceRequest, response *tfsdk.CreateResourceResponse) { - if !r.p.configured { - response.Diagnostics.AddError( - "Provider not configured", - "The provider hasn't been configured before apply, likely because it depends on an unknown value from another resource. This leads to weird stuff happening, so we'd prefer if you didn't do that. Thanks!", - ) - return - } - - // Retrieve values from plan - var plan SecurityIdentity - diags := request.Plan.Get(ctx, &plan) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Generate API request body from plan - - kfClient := r.p.client - - accountName := plan.AccountName.Value - ctx = tflog.SetField(ctx, "account_name", accountName) - tflog.Info(ctx, "Creating Keyfactor security identity resource") - - identityArg := &api.CreateSecurityIdentityArg{ - AccountName: accountName, - } - - createResponse, err := kfClient.CreateSecurityIdentity(identityArg) - if err != nil { - response.Diagnostics.AddError( - "Error creating security identity.", - "Could not create identity "+plan.AccountName.Value+", unexpected error: "+err.Error(), - ) - return - } - - // for more information on logging from providers, refer to - // https://pkg.go.dev/github.com/hashicorp/terraform-plugin-log/tflog - tflog.Trace(ctx, "created security id", map[string]interface{}{"identity_account_name": plan.AccountName.Value}) - var validRoles []attr.Value - if len(plan.Roles.Elems) > 0 { - var validRolesInterface []interface{} - for _, role := range plan.Roles.Elems { - //validRoles = append(validRoles.Elems, role.Name.Value) - tflog.Info(ctx, fmt.Sprintf("Adding role: %s", role)) - tflog.Debug(ctx, fmt.Sprintf("Looking up role %v in Keyfactor", role)) - - //TODO: Verify role exists in Keyfactor or throw warning - re, _ := regexp.Compile(`[^\w]`) - roleStr := re.ReplaceAllString(role.String(), "") - tflog.Debug(ctx, fmt.Sprintf("Role string: %s", roleStr)) - kfRole, roleLookupErr := r.p.client.GetSecurityRole(roleStr) - if roleLookupErr != nil || kfRole == nil { - tflog.Warn(ctx, fmt.Sprintf("Error looking up role with id: %s", role)) - response.Diagnostics.AddWarning( - "Error looking up role on Keyfactor.", - fmt.Sprintf("Error looking up role '%s' on Keyfactor. %s will not have role %s.", roleStr, accountName, roleStr), - ) - continue - } - validRoles = append(validRoles, types.String{Value: fmt.Sprintf("%s", roleStr)}) - validRolesInterface = append(validRolesInterface, kfRole.Id) - } - err = setIdentityRole(ctx, kfClient, identityArg.AccountName, validRolesInterface) - if err != nil { - response.Diagnostics.AddError("Error updating identity roles.", "Error updating identity roles: "+err.Error()) - } - } - - if validRoles == nil { - validRoles = plan.Roles.Elems - } - // Generate resource state struct - var result = SecurityIdentity{ - ID: types.Int64{Value: int64(createResponse.Id)}, - AccountName: types.String{Value: accountName}, - IdentityType: types.String{Value: plan.IdentityType.Value}, - Valid: types.Bool{Value: plan.Valid.Value}, - Roles: plan.Roles, - } - - diags = response.State.Set(ctx, result) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } -} - -func (r resourceSecurityIdentity) ImportState(ctx context.Context, request tfsdk.ImportResourceStateRequest, response *tfsdk.ImportResourceStateResponse) { - ctx = context.WithValue(ctx, "import", true) - var state SecurityIdentity - - tflog.Info(ctx, "Read called on security identity resource") - accountName := request.ID - - identities, err := r.p.client.GetSecurityIdentities() - - if err != nil { - response.Diagnostics.AddError("Error listing identities from Keyfactor.", "Error reading identities: "+err.Error()) - } - - identityExists := false - for _, identity := range identities { - if accountName == identity.AccountName { - tflog.Info(ctx, fmt.Sprintf("Found identity with account name: %s", accountName)) - identityExists = true - var roles []attr.Value - for _, role := range identity.Roles { - roles = append(roles, types.String{Value: role.Name}) - } - state = SecurityIdentity{ - ID: types.Int64{Value: int64(identity.Id)}, - AccountName: types.String{Value: identity.AccountName}, - IdentityType: types.String{Value: identity.IdentityType}, - Roles: types.List{Elems: roles, ElemType: types.StringType}, - Valid: types.Bool{Value: identity.Valid}, - } - - break - } - - } - - if !identityExists { - response.Diagnostics.AddError("Unknown identity error.", fmt.Sprintf("Unable to find identity %s on Keyfactor. Import failed.", accountName)) - return - } - - diags := response.State.Set(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } -} - -func setIdentityRole(ctx context.Context, kfClient *api.Client, identityAccountName string, roleIds []interface{}) error { - // Basic idea here is that we want to sync the output of the GET identity endpoint with the roleIds passed to - // this function. This could mean that we are removing the identity from a role, adding an identity, or not making - // any change. This is required because no PUT endpoint exists for /identity. - - // Start by blindly adding the identity to each role. - if len(roleIds) > 0 { - var roleId int - for _, role := range roleIds { - switch role.(type) { - case int: - roleId = role.(int) - - case string, interface{}: - roleId = role.(int) - } - err := addIdentityToRole(ctx, kfClient, identityAccountName, roleId) - if err != nil { - return err - } - } - } - - // Then, build a list of all roles associated with the identity and make sure that only the ones specified by - // this function are added. - // Get all Keyfactor security identities - identities, err := kfClient.GetSecurityIdentities() - if err != nil { - return err - } - var identity api.GetSecurityIdentityResponse - for _, identity = range identities { - if strings.ToLower(identity.AccountName) == strings.ToLower(identityAccountName) { - break - } - } - - // Now, build a list of the roles associated with the identity. Note that any differences found here will be removals - // because we already added the roles that we want above. The below method doesn't require the slices to be sorted, - // and operates at approximately O(n) - - list := make(map[string]struct{}, len(roleIds)) - for _, x := range roleIds { - list[strconv.Itoa(x.(int))] = struct{}{} - } - var diff []int - for _, x := range identity.Roles { - if _, found := list[strconv.Itoa(x.Id)]; !found { - diff = append(diff, x.Id) - } - } - - for _, role := range diff { - err = removeIdentityFromRole(kfClient, identity.AccountName, role) - if err != nil { - return err - } - } - return nil -} - -func removeIdentityFromRole(kfClient *api.Client, identityAccountName string, roleId int) error { - log.Printf("[DEBUG] Removing account %s from Keyfactor role %d", identityAccountName, roleId) - // Construct a list of security identities currently attached to role - role, err := kfClient.GetSecurityRole(roleId) - if err != nil { - return err - } - var identityList []api.SecurityRoleIdentityConfig - for _, identity := range role.Identities { - if strings.ToLower(identityAccountName) != strings.ToLower(identity.AccountName) { - temp := api.SecurityRoleIdentityConfig{ - AccountName: identity.AccountName, - } - identityList = append(identityList, temp) - } - } - - // Note - update security role wraps the create role structure but compiles to the desired JSON request body. - updateArg := &api.UpdateSecurityRoleArg{ - Id: roleId, - CreateSecurityRoleArg: api.CreateSecurityRoleArg{ - Name: role.Name, - Identities: &identityList, - Description: role.Description, - Permissions: &role.Permissions, - }, - } - - _, err = kfClient.UpdateSecurityRole(updateArg) - if err != nil { - return err - } - - return nil -} - -func addIdentityToRole(ctx context.Context, kfClient *api.Client, identityAccountName string, roleId int) error { - ctx = tflog.SetField(ctx, "role_id", roleId) - ctx = tflog.SetField(ctx, "identity_account_name", identityAccountName) - tflog.Debug(ctx, "Adding account to Keyfactor role.") - // Construct a list of security identities currently attached to role - role, err := kfClient.GetSecurityRole(roleId) - if err != nil { - return err - } - - identityList := make([]api.SecurityRoleIdentityConfig, len(role.Identities)) - for i, identity := range role.Identities { - if identity.AccountName == identityAccountName { - tflog.Debug(ctx, "Account is already associated with Keyfactor role.") - return nil - } - temp := api.SecurityRoleIdentityConfig{ - AccountName: identity.AccountName, - } - identityList[i] = temp - } - - // Add new identity to identity list and update role - temp := api.SecurityRoleIdentityConfig{ - AccountName: identityAccountName, - } - identityList = append(identityList, temp) - - // Note - update security role wraps the create role structure but compiles to the desired JSON request body. - updateArg := &api.UpdateSecurityRoleArg{ - Id: roleId, - CreateSecurityRoleArg: api.CreateSecurityRoleArg{ - Name: role.Name, - Identities: &identityList, - Description: role.Description, - Permissions: &role.Permissions, - }, - } - - _, err = kfClient.UpdateSecurityRole(updateArg) - if err != nil { - return err - } - - return nil -} diff --git a/keyfactor/resource_keyfactor_security_identity_test.go b/keyfactor/resource_keyfactor_security_identity_test.go deleted file mode 100644 index 0d64087b..00000000 --- a/keyfactor/resource_keyfactor_security_identity_test.go +++ /dev/null @@ -1,121 +0,0 @@ -package keyfactor - -import ( - "encoding/json" - "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "os" - "testing" -) - -type identityTestCase struct { - accountName string - roles []string - resourceName string - rolesStr string -} - -func TestAccKeyfactorIdentityResource(t *testing.T) { - // Single role test - i := identityTestCase{ - accountName: os.Getenv("KEYFACTOR_SECURITY_IDENTITY_ACCOUNTNAME"), - roles: []string{ - os.Getenv("KEYFACTOR_SECURITY_IDENTITY_ROLE1"), - }, - resourceName: "keyfactor_identity.terraformer", - } - - rStr, _ := json.Marshal(i.roles) - i.rolesStr = string(rStr) - - // Update to multiple roles test - i2 := i - i2.roles = append(i2.roles, os.Getenv("KEYFACTOR_SECURITY_IDENTITY_ROLE2")) - r2Str, _ := json.Marshal(i2.roles) - i2.rolesStr = string(r2Str) - - // Update to no roles test - i3 := i2 - i3.roles = []string{} - r3Str, _ := json.Marshal(i3.roles) - i3.rolesStr = string(r3Str) - - // Testing Identity - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - // Create and Read testing - { - //ResourceName: "", - //PreConfig: nil, - //Taint: nil, - Config: testAccKeyfactorIdentityResourceConfig(i), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(i.resourceName, "id"), - resource.TestCheckResourceAttrSet(i.resourceName, "account_name"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(i.resourceName, "roles.0"), // TODO: Check specific value - - ), - //Destroy: false, - //ExpectNonEmptyPlan: false, - //ExpectError: nil, - //PlanOnly: false, - //PreventDiskCleanup: false, - //PreventPostDestroyRefresh: false, - //SkipFunc: nil, - //ImportState: false, - //ImportStateId: "", - //ImportStateIdPrefix: "", - //ImportStateIdFunc: nil, - //ImportStateCheck: nil, - //ImportStateVerify: false, - //ImportStateVerifyIgnore: nil, - //ProviderFactories: nil, - //ProtoV5ProviderFactories: nil, - //ProtoV6ProviderFactories: nil, - //ExternalProviders: nil, - }, - // ImportState testing - //{ - // ResourceName: "scaffolding_example.test", - // ImportState: false, - // ImportStateVerify: false, - // // This is not normally necessary, but is here because this - // // example code does not have an actual upstream service. - // // Once the Read method is able to refresh information from - // // the upstream service, this can be removed. - // ImportStateVerifyIgnore: []string{"configurable_attribute"}, - //}, - // Update and Read testing - { - Config: testAccKeyfactorIdentityResourceConfig(i2), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(i2.resourceName, "id"), - resource.TestCheckResourceAttrSet(i2.resourceName, "account_name"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(i2.resourceName, "roles.0"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(i2.resourceName, "roles.1"), // TODO: Check specific value - ), - }, - { - Config: testAccKeyfactorIdentityResourceConfig(i3), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(i3.resourceName, "id"), - resource.TestCheckResourceAttrSet(i3.resourceName, "account_name"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(i3.resourceName, "roles.#"), - ), - }, - // Delete testing automatically occurs in TestCase - }, - }) -} - -func testAccKeyfactorIdentityResourceConfig(t identityTestCase) string { - output := fmt.Sprintf(` -resource "keyfactor_identity" "terraformer" { - account_name = "%s" - roles = sort(%s) -} -`, t.accountName, t.rolesStr) - return output -} diff --git a/keyfactor/resource_keyfactor_security_role.go b/keyfactor/resource_keyfactor_security_role.go deleted file mode 100644 index 87df4ceb..00000000 --- a/keyfactor/resource_keyfactor_security_role.go +++ /dev/null @@ -1,277 +0,0 @@ -package keyfactor - -import ( - "context" - "fmt" - "github.com/Keyfactor/keyfactor-go-client/api" - "github.com/hashicorp/terraform-plugin-framework/attr" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" - "sort" -) - -type resourceSecurityRoleType struct{} - -func (r resourceSecurityRoleType) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfsdk.Schema{ - Attributes: map[string]tfsdk.Attribute{ - "id": { - Type: types.Int64Type, - Computed: true, - }, - "name": { - Type: types.StringType, - Required: true, - Description: "An string associated with a Keyfactor security role.", - }, - "description": { - Type: types.StringType, - Required: true, - Description: "A string containing the description of the role in Keyfactor", - }, - "permissions": { - Type: types.ListType{ElemType: types.StringType}, - Optional: true, - Description: "An array containing the permissions assigned to the role in a list of Name:Value pairs", - }, - }, - }, nil -} - -// New resource instance -func (r resourceSecurityRoleType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourceSecurityRole{ - p: *(p.(*provider)), - }, nil -} - -type resourceSecurityRole struct { - p provider -} - -func (r resourceSecurityRole) Read(ctx context.Context, request tfsdk.ReadResourceRequest, response *tfsdk.ReadResourceResponse) { - var state SecurityRole - diags := request.State.Get(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - tflog.Info(ctx, "Read called on security remoteState resource") - roleId := state.ID.Value - //roleName := state.Name.Value - tflog.SetField(ctx, "role_id", roleId) - - //remoteState, err := r.p.client.GetSecurityRole(int(roleId)) - //if remoteState == nil { - // response.Diagnostics.AddError("Unknown role error.", fmt.Sprintf("Unable to find role '%s' on Keyfactor. Read failed.", roleName)) - // return - //} - - //if err != nil { - // response.Diagnostics.AddError("Error listing roles from Keyfactor.", "Error reading roles: "+err.Error()) - // return - //} - - diags = response.State.Set(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } -} - -func (r resourceSecurityRole) Update(ctx context.Context, request tfsdk.UpdateResourceRequest, response *tfsdk.UpdateResourceResponse) { - // Get plan values - var plan SecurityRole - diags := request.Plan.Get(ctx, &plan) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - tflog.Info(ctx, "Update called on security identity resource") - - // Get current state - var state SecurityRole - diags = request.State.Get(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - roleId := state.ID.Value - tflog.SetField(ctx, "id", roleId) - - // Generate API request body from plan - - var permissions []string - plan.Permissions.ElementsAs(ctx, &permissions, false) - //Update role identities - updateArg := &api.UpdateSecurityRoleArg{ - Id: int(roleId), - CreateSecurityRoleArg: api.CreateSecurityRoleArg{ - Name: plan.Name.Value, - Description: plan.Description.Value, - Permissions: &permissions, - }, - } - - remoteState, err := r.p.client.UpdateSecurityRole(updateArg) - if err != nil { - response.Diagnostics.AddError("Identity role update error.", fmt.Sprintf("Error updating identity role '%s': "+err.Error(), plan.Name.Value)) - return - } - - var permissionValues []attr.Value - sort.Strings(*remoteState.Permissions) - for _, perm := range *remoteState.Permissions { - tflog.Info(ctx, "Permission: "+perm) - permissionValues = append(permissionValues, types.String{ - Value: perm, - }) - } - - var result = SecurityRole{ - ID: types.Int64{Value: int64(state.ID.Value)}, - Name: types.String{Value: remoteState.Name}, - Description: types.String{Value: remoteState.Description}, - Permissions: types.List{ElemType: types.StringType, Elems: permissionValues}, - } - - // Set state - diags = response.State.Set(ctx, result) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } -} - -func (r resourceSecurityRole) Delete(ctx context.Context, request tfsdk.DeleteResourceRequest, response *tfsdk.DeleteResourceResponse) { - var state SecurityRole - diags := request.State.Get(ctx, &state) - kfClient := r.p.client - - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Get order ID from state - identityId := state.ID.Value - - // Delete order by calling API - err := kfClient.DeleteSecurityRole(int(identityId)) - if err != nil { - response.Diagnostics.AddError( - "Error deleting security identity.", - "Could not delete "+state.Name.Value+" from Keyfactor: "+err.Error(), - ) - return - } - - // Remove resource from state - response.State.RemoveResource(ctx) - -} - -func (r resourceSecurityRole) Create(ctx context.Context, request tfsdk.CreateResourceRequest, response *tfsdk.CreateResourceResponse) { - if !r.p.configured { - response.Diagnostics.AddError( - "Provider not configured", - "The provider hasn't been configured before apply, likely because it depends on an unknown value from another resource. This leads to weird stuff happening, so we'd prefer if you didn't do that. Thanks!", - ) - return - } - - // Retrieve values from plan - var plan SecurityRole - diags := request.Plan.Get(ctx, &plan) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Generate API request body from plan - - kfClient := r.p.client - - roleName := plan.Name.Value - ctx = tflog.SetField(ctx, "role_name", roleName) - tflog.Info(ctx, "Creating Keyfactor security identity resource") - - var permissions []string - plan.Permissions.ElementsAs(ctx, &permissions, false) - sort.Strings(permissions) - - roleArg := &api.CreateSecurityRoleArg{ - Name: roleName, - Description: plan.Description.Value, - Permissions: &permissions, - } - - createResponse, err := kfClient.CreateSecurityRole(roleArg) - if err != nil { - response.Diagnostics.AddError( - "Error creating security identity.", - "Could not create identity "+plan.Name.Value+", unexpected error: "+err.Error(), - ) - return - } - tflog.Trace(ctx, "Created security role", map[string]interface{}{"role_name": plan.Name.Value}) - - var result = SecurityRole{ - ID: types.Int64{Value: int64(createResponse.Id)}, - Name: types.String{Value: createResponse.Name}, - Description: types.String{Value: createResponse.Description}, - Permissions: plan.Permissions, - } - - diags = response.State.Set(ctx, result) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } -} - -func (r resourceSecurityRole) ImportState(ctx context.Context, request tfsdk.ImportResourceStateRequest, response *tfsdk.ImportResourceStateResponse) { - tflog.Info(ctx, "Read called on security remoteState resource") - roleId := request.ID - //roleName := state.Name.Value - tflog.SetField(ctx, "role_id", roleId) - //_, err := strconv.Atoi(roleId) - - remoteState, err := r.p.client.GetSecurityRole(roleId) - if remoteState == nil { - response.Diagnostics.AddError("Unknown role error.", fmt.Sprintf("Unable to find role '%v' on Keyfactor. Read failed. ", roleId)) - return - } - - if err != nil { - response.Diagnostics.AddError("Unknown role error.", fmt.Sprintf("Unknown error while trying to import role '%v' on Keyfactor. Read failed. "+err.Error(), roleId)) - return - } - - var permissionValues []attr.Value - for _, perm := range remoteState.Permissions { - tflog.Debug(ctx, fmt.Sprintf("Permission: %v", perm)) - permissionValues = append(permissionValues, types.String{Value: perm}) - } - - var result = SecurityRole{ - ID: types.Int64{Value: int64(remoteState.Id)}, - Name: types.String{Value: remoteState.Name}, - Description: types.String{Value: remoteState.Description}, - Permissions: types.List{ElemType: types.StringType, Elems: permissionValues}, - } - - diags := response.State.Set(ctx, result) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - if response.Diagnostics.HasError() { - return - } -} diff --git a/keyfactor/resource_keyfactor_security_role_test.go b/keyfactor/resource_keyfactor_security_role_test.go deleted file mode 100644 index fff7fce8..00000000 --- a/keyfactor/resource_keyfactor_security_role_test.go +++ /dev/null @@ -1,146 +0,0 @@ -package keyfactor - -import ( - "encoding/json" - "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "os" - "testing" -) - -type roleTestCase struct { - name string - description string - permissions []string - permissionsStr string - resourceName string -} - -func TestAccKeyfactorRoleResource(t *testing.T) { - - r := roleTestCase{ - name: os.Getenv("KEYFACTOR_SECURITY_ROLE_NAME"), - description: "Role used for a Terraform.", - permissions: []string{ - "AdminPortal:Read", - "API:Read", - }, - resourceName: "keyfactor_role.terraform_test", - } - pStr, _ := json.Marshal(r.permissions) - r.permissionsStr = string(pStr) - - // Update to multiple roles test - r2 := r - additionalPermissions := []string{ - "Certificates:Read", - "Certificates:EditMetadata", - "Certificates:Import", - "Certificates:Recover", - "Certificates:Revoke", - "Certificates:Delete", - "Certificates:ImportPrivateKey", - "CertificateCollections:Modify", - "PkiManagement:Read", - "PkiManagement:Modify", - "CertificateStoreManagement:Read", - "CertificateStoreManagement:Modify", - "CertificateStoreManagement:Schedule", - "CertificateEnrollment:EnrollPFX", - "CertificateEnrollment:EnrollCSR", - "CertificateEnrollment:CsrGeneration", - "CertificateEnrollment:PendingCsr", - } - r2.permissions = append(r2.permissions, additionalPermissions...) - r2Str, _ := json.Marshal(r2.permissions) - r2.permissionsStr = string(r2Str) - - // Update to no roles test - r3 := r2 - r3.permissions = []string{} - r3Str, _ := json.Marshal(r3.permissions) - r3.permissionsStr = string(r3Str) - - // Testing Role - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - // Create and Read testing - { - //ResourceName: "", - //PreConfig: nil, - //Taint: nil, - Config: testAccKeyfactorRoleResourceConfig(r), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(r.resourceName, "id"), - resource.TestCheckResourceAttrSet(r.resourceName, "name"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r.resourceName, "permissions.0"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r.resourceName, "permissions.1"), // TODO: Check specific value - - ), - //Destroy: false, - //ExpectNonEmptyPlan: false, - //ExpectError: nil, - //PlanOnly: false, - //PreventDiskCleanup: false, - //PreventPostDestroyRefresh: false, - //SkipFunc: nil, - //ImportState: false, - //ImportStateId: "", - //ImportStateIdPrefix: "", - //ImportStateIdFunc: nil, - //ImportStateCheck: nil, - //ImportStateVerify: false, - //ImportStateVerifyIgnore: nil, - //ProviderFactories: nil, - //ProtoV5ProviderFactories: nil, - //ProtoV6ProviderFactories: nil, - //ExternalProviders: nil, - }, - // ImportState testing - //{ - // ResourceName: "scaffolding_example.test", - // ImportState: false, - // ImportStateVerify: false, - // // This is not normally necessary, but is here because this - // // example code does not have an actual upstream service. - // // Once the Read method is able to refresh information from - // // the upstream service, this can be removed. - // ImportStateVerifyIgnore: []string{"configurable_attribute"}, - //}, - // Update and Read testing - { - Config: testAccKeyfactorRoleResourceConfig(r2), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(r2.resourceName, "id"), - resource.TestCheckResourceAttrSet(r2.resourceName, "name"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r2.resourceName, "permissions.0"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r2.resourceName, "permissions.1"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r2.resourceName, "permissions.3"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r2.resourceName, "permissions.4"), // TODO: Check specific value - ), - }, - { - Config: testAccKeyfactorRoleResourceConfig(r3), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(r3.resourceName, "id"), - resource.TestCheckResourceAttrSet(r3.resourceName, "name"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r3.resourceName, "permissions.#"), - ), - }, - // Delete testing automatically occurs in TestCase - }, - }) -} - -func testAccKeyfactorRoleResourceConfig(t roleTestCase) string { - output := fmt.Sprintf(` -resource "keyfactor_role" "terraform_test" { - name = "%s" - description = "%s" - permissions = distinct(sort(%s)) -} -`, t.name, t.description, t.permissionsStr) - return output -} diff --git a/keyfactor/resource_keyfactor_template_role_binding.go b/keyfactor/resource_keyfactor_template_role_binding.go deleted file mode 100644 index 427acc15..00000000 --- a/keyfactor/resource_keyfactor_template_role_binding.go +++ /dev/null @@ -1,663 +0,0 @@ -package keyfactor - -import ( - "context" - "crypto/sha256" - "fmt" - "github.com/Keyfactor/keyfactor-go-client/api" - "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/tfsdk" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "strconv" - "strings" -) - -type resourceCertificateTemplateRoleBindingType struct{} - -func (r resourceCertificateTemplateRoleBindingType) GetSchema(_ context.Context) (tfsdk.Schema, diag.Diagnostics) { - return tfsdk.Schema{ - Attributes: map[string]tfsdk.Attribute{ - "id": { - Type: types.StringType, - Computed: true, - Description: "ID of template role binding.", - }, - "role_name": { - Type: types.StringType, - Required: true, - Description: "An string associated with a Keyfactor security role being attached. This is just the name field found on Keyfactor.", - }, - "template_short_names": { - Type: types.ListType{ElemType: types.StringType}, - Optional: true, - Description: "A list of certificate template short name in Keyfactor that the role will be attached to.", - }, - }, - }, nil -} - -func (r resourceCertificateTemplateRoleBindingType) NewResource(_ context.Context, p tfsdk.Provider) (tfsdk.Resource, diag.Diagnostics) { - return resourceCertificateTemplateRoleBinding{ - p: *(p.(*provider)), - }, nil -} - -type resourceCertificateTemplateRoleBinding struct { - p provider -} - -func (r resourceCertificateTemplateRoleBinding) Create(ctx context.Context, request tfsdk.CreateResourceRequest, response *tfsdk.CreateResourceResponse) { - if !r.p.configured { - response.Diagnostics.AddError( - "Provider not configured", - "The provider hasn't been configured before apply, likely because it depends on an unknown value from another resource. This leads to weird stuff happening, so we'd prefer if you didn't do that. Thanks!", - ) - return - } - - // Retrieve values from plan - var plan CertificateTemplateRoleBinding - diags := request.Plan.Get(ctx, &plan) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Generate API request body from plan - kfClient := r.p.client - roleName := plan.RoleName.Value - - tflog.Info(ctx, "Create called on certificate template role binding.") - - // Verify template names - var templateNames []string - var validTemplateIds []int - var apiDiags []diag.Diagnostic - - diags = plan.TemplateNames.ElementsAs(ctx, &templateNames, true) - - tNameStr := strings.Join(templateNames, "-") - - hid := fmt.Sprintf("%s-%s", roleName, tNameStr) - ctx = tflog.SetField(ctx, "role_binding_id", hid) - - // List all templates - kfTemplates, err := kfClient.GetTemplates() - if err != nil { - response.Diagnostics.AddError( - "Error getting templates", - "There was an error getting templates from Keyfactor: "+err.Error(), - ) - return - } - validTemplateIds, apiDiags = verifyTemplateNames(ctx, kfTemplates, templateNames) - tflog.Debug(ctx, fmt.Sprintf("Valid template IDs: %v", validTemplateIds)) - response.Diagnostics.Append(apiDiags...) - if response.Diagnostics.HasError() { - return - } - - // Create role binding - diags = setRoleAllowedRequester(ctx, kfClient, roleName, validTemplateIds, []int{}, "create") - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Set state - result := CertificateTemplateRoleBinding{ - ID: types.String{Value: fmt.Sprintf("%x", sha256.Sum256([]byte(hid)))}, - RoleName: plan.RoleName, - TemplateNames: plan.TemplateNames, - } - diags = response.State.Set(ctx, result) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - -} - -func (r resourceCertificateTemplateRoleBinding) Read(ctx context.Context, request tfsdk.ReadResourceRequest, response *tfsdk.ReadResourceResponse) { - var state CertificateTemplateRoleBinding - diags := request.State.Get(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Generate API request body from plan - - kfClient := r.p.client - roleName := state.RoleName.Value - - tflog.Info(ctx, "Create called on certificate template role binding.") - - var templateNames []string - var validTemplateIds []int - var apiDiags []diag.Diagnostic - diags = state.TemplateNames.ElementsAs(ctx, &templateNames, true) - kfTemplates, err := kfClient.GetTemplates() - if err != nil { - return - } - validTemplateIds, apiDiags = verifyTemplateNames(ctx, kfTemplates, templateNames) - tflog.Debug(ctx, fmt.Sprintf("Valid template IDs: %v", validTemplateIds)) - response.Diagnostics.Append(apiDiags...) - if response.Diagnostics.HasError() { - return - } - - hid := fmt.Sprintf("%v%v", roleName, templateNames) - ctx = tflog.SetField(ctx, "role_binding_id", hid) - - // Set state - diags = response.State.Set(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } -} - -func (r resourceCertificateTemplateRoleBinding) Update(ctx context.Context, request tfsdk.UpdateResourceRequest, response *tfsdk.UpdateResourceResponse) { - // Get plan values - var plan CertificateTemplateRoleBinding - diags := request.Plan.Get(ctx, &plan) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Get current state - var state CertificateTemplateRoleBinding - diags = request.State.Get(ctx, &state) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Generate API request body from plan - kfClient := r.p.client - roleName := state.RoleName.Value - - tflog.Info(ctx, "Create called on certificate template role binding.") - - // Validate template names - var stateTemplateNames []string - var planTemplateNames []string - var stateValidTemplateIds []int - var apiDiags []diag.Diagnostic - diags = state.TemplateNames.ElementsAs(ctx, &stateTemplateNames, true) - diags = plan.TemplateNames.ElementsAs(ctx, &planTemplateNames, true) - kfTemplates, err := kfClient.GetTemplates() - if err != nil { - return - } - stateValidTemplateIds, apiDiags = verifyTemplateNames(ctx, kfTemplates, stateTemplateNames) - planValidTemplateIds, apiDiags := verifyTemplateNames(ctx, kfTemplates, planTemplateNames) - tflog.Debug(ctx, fmt.Sprintf("Valid template IDs: %v", stateValidTemplateIds)) - response.Diagnostics.Append(apiDiags...) - if response.Diagnostics.HasError() { - return - } - - // Set binding ID - hid := fmt.Sprintf("%v%v", roleName, stateTemplateNames) - ctx = tflog.SetField(ctx, "role_binding_id", hid) - - // Set role allowed requester - diags = setRoleAllowedRequester(ctx, kfClient, roleName, stateValidTemplateIds, planValidTemplateIds, "update") - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Set state - result := CertificateTemplateRoleBinding{ - ID: state.ID, - RoleName: plan.RoleName, - TemplateNames: plan.TemplateNames, - } - diags = response.State.Set(ctx, result) - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } -} - -func (r resourceCertificateTemplateRoleBinding) Delete(ctx context.Context, request tfsdk.DeleteResourceRequest, response *tfsdk.DeleteResourceResponse) { - var state CertificateTemplateRoleBinding - diags := request.State.Get(ctx, &state) - - kfClient := r.p.client - roleName := state.RoleName.Value - - // Verify template names - var templateNames []string - var validTemplateIds []int - var apiDiags []diag.Diagnostic - - diags = state.TemplateNames.ElementsAs(ctx, &templateNames, true) - - hid := fmt.Sprintf("%v%v", roleName, templateNames) - ctx = tflog.SetField(ctx, "role_binding_id", hid) - - // List all templates - kfTemplates, err := kfClient.GetTemplates() - if err != nil { - response.Diagnostics.AddError( - "Error getting templates", - "There was an error getting templates from Keyfactor: "+err.Error(), - ) - return - } - validTemplateIds, apiDiags = verifyTemplateNames(ctx, kfTemplates, templateNames) - tflog.Debug(ctx, fmt.Sprintf("Valid template IDs: %v", validTemplateIds)) - response.Diagnostics.Append(apiDiags...) - if response.Diagnostics.HasError() { - return - } - - // Create role binding - diags = setRoleAllowedRequester(ctx, kfClient, roleName, validTemplateIds, []int{}, "destroy") - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - - // Remove resource from state - response.State.RemoveResource(ctx) - -} - -func (r resourceCertificateTemplateRoleBinding) ImportState(ctx context.Context, request tfsdk.ImportResourceStateRequest, response *tfsdk.ImportResourceStateResponse) { - var state CertificateTemplateRoleBinding - var diags diag.Diagnostics - //diags := request.ID - diags.AddError("Import not implemented", "Import is not implemented for this resource") - response.Diagnostics.Append(diags...) - if response.Diagnostics.HasError() { - return - } - response.State.Set(ctx, state) -} - -func verifyTemplateNames(ctx context.Context, templates []api.GetTemplateResponse, templateNames []string) ([]int, []diag.Diagnostic) { - var diags diag.Diagnostics - var result []int - for _, templateName := range templateNames { - if templateName == "" { - diags.AddError("Error empty template name.", "Template name provided") - } - found := false - for _, template := range templates { - if strings.EqualFold(template.CommonName, templateName) { - //result.Elems = append(result.Elems, types.Int64{Value: int64(template.Id)}) - result = append(result, template.Id) - found = true - break - } - } - if !found { - diags.AddError("Error template name not found.", "Template name "+templateName+" not found") - } - - } - return result, diags -} - -/* - * The resourceTemplateAttachRoleUpdate function is responsible for updating a Keyfactor security role. - */ -func setRoleAllowedRequester(ctx context.Context, kfClient *api.Client, roleName string, stateTemplateSet []int, planTemplateSet []int, caller string) diag.Diagnostics { - var diags diag.Diagnostics - - ctx = tflog.SetField(ctx, "role_name", roleName) - tflog.Debug(ctx, "Setting Keyfactor role with name to be allowed requester for the following templates:") - - templateList := stateTemplateSet - tflog.Debug(ctx, "Template IDs: ", map[string]interface{}{ - "template_ids": templateList, - }) - - // First thing to do is blindly attach the passed role as an allowed requester to each of the template IDs passed in - // the Set. - if len(templateList) > 0 { - for _, template := range templateList { - tempCtx := tflog.SetField(ctx, "template_id", template) - tempCtx = tflog.SetField(tempCtx, "role_name", roleName) - tflog.Info(tempCtx, "Attaching role to template ID ") - err := addAllowedRequesterToTemplate(ctx, kfClient, roleName, strconv.Itoa(template)) - if err != nil { - tflog.Error(tempCtx, "Error attaching role to template") - diags = append(diags, err...) - } - } - } - - if diags != nil { - return diags - } - - // Then, build a list of all templates that the role is attached to as an allowed requester - tflog.Debug(ctx, "Building list of templates that the role is attached to as an allowed requester") - err, roleAttachments := findTemplateRoleAttachments(ctx, kfClient, roleName) - if err != nil { - return err - } - - // Finally, find the difference between stateTemplateSet and roleAttachments. Recall that Terraform acts as the primary - // manager of the role roleName, and Terraform is calling this function to explicitly set the allowed requesters. - - tflog.Debug(ctx, "Finding difference between stateTemplateSet and roleAttachments") - list := make(map[string]struct{}, len(templateList)) - for _, x := range templateList { - list[strconv.Itoa(x)] = struct{}{} - } - var diff []int - for _, x := range roleAttachments { - if _, found := list[strconv.Itoa(x.(int))]; !found { - tflog.Debug(ctx, "Found difference between stateTemplateSet and roleAttachments", map[string]interface{}{ - "template_id": x, - }) - diff = append(diff, x.(int)) - } - } - additions := []int{} - removals := []int{} - - for _, templateId := range planTemplateSet { - if !Contains(stateTemplateSet, templateId) { - additions = append(additions, templateId) - } - } - - for _, templateId := range stateTemplateSet { - if !Contains(planTemplateSet, templateId) { - removals = append(removals, templateId) - } - } - - if caller == "destroy" { - removals = append(removals, additions...) - additions = []int{} - } else if caller == "create" { - additions = append(additions, removals...) - removals = []int{} - } - - tflog.Debug(ctx, "Adding difference between stateTemplateSet and roleAttachments") - for _, template := range additions { - tempCtx := tflog.SetField(ctx, "template_id", template) - tempCtx = tflog.SetField(tempCtx, "role_name", roleName) - tflog.Info(tempCtx, "Attaching role to template ID ") - aErr := addAllowedRequesterToTemplate(ctx, kfClient, roleName, strconv.Itoa(template)) - if aErr != nil { - tflog.Error(tempCtx, "Error attaching role to template") - diags = append(diags, aErr...) - } - } - - tflog.Debug(ctx, "Removing difference between stateTemplateSet and roleAttachments") - for _, template := range removals { - tempCtx := tflog.SetField(ctx, "template_id", template) - tempCtx = tflog.SetField(tempCtx, "role_name", roleName) - tflog.Info(tempCtx, "Detaching role from template ID ") - rErr := removeRoleFromTemplate(ctx, kfClient, roleName, template) - if rErr != nil { - tflog.Error(tempCtx, "Error detaching role from template") - diags = append(diags, rErr...) - } - } - - tflog.Debug(ctx, "Removing difference between stateTemplateSet and roleAttachments") - //for _, template := range diff { - // tempCtx := tflog.SetField(ctx, "template_id", template) - // tflog.Debug(tempCtx, "Removing role from template") - // err = removeRoleFromTemplate(ctx, kfClient, roleName, template) - // if err != nil { - // tflog.Error(tempCtx, "Error removing role from template") - // diags = append(diags, err...) - // } - //} - // - //tflog.Info(ctx, "Finished binding roles to templates") - return diags -} - -func Contains(sl []int, val int) bool { - for _, value := range sl { - if value == val { - return true - } - } - return false -} - -/* - * The resourceTemplateAttachRoleRead function is responsible for reading a Keyfactor security role. - */ -func addAllowedRequesterToTemplate(ctx context.Context, kfClient *api.Client, roleName string, templateId string) diag.Diagnostics { - var diags diag.Diagnostics - ctx = tflog.SetField(ctx, "template_id", templateId) - ctx = tflog.SetField(ctx, "role_name", roleName) - tflog.Debug(ctx, "Adding Keyfactor role with ID to template with ID") - - // First get info about template from Keyfactor - if _, err := strconv.Atoi(templateId); err == nil { - tflog.Debug(ctx, fmt.Sprintf("Getting template info from Keyfactor using ID '%s'", templateId)) - } - templateIdNumber, err := strconv.Atoi(templateId) - ctx = tflog.SetField(ctx, "template_id", templateIdNumber) - if err != nil { - tflog.Info(ctx, "Assuming templateId is a short name") - tflog.Debug(ctx, "Fetching templates from Keyfactor") - templates, err2 := kfClient.GetTemplates() - if err2 != nil { - tflog.Error(ctx, "Error fetching templates from Keyfactor", map[string]interface{}{ - "error": err2, - }) - diags.AddError("Error fetching templates from Keyfactor", err2.Error()) - } - tflog.Debug(ctx, "Finding template in returned templates") - for template := range templates { - tflog.Debug(ctx, "Looking up template", map[string]interface{}{ - "template": template, - }) - kfTemplate, err3 := kfClient.GetTemplate(template) - if err3 != nil { - tflog.Error(ctx, "Error fetching template from Keyfactor", map[string]interface{}{ - "error": err3, - }) - diags.AddError("Error fetching template from Keyfactor", err3.Error()) - continue - } - tflog.Debug(ctx, "Found template", map[string]interface{}{ - "template": kfTemplate, - }) - if kfTemplate.CommonName == templateId { - tflog.Debug(ctx, "Found matching template.") - templateIdNumber = kfTemplate.Id - break - } - } - } - - tflog.Debug(ctx, "Fetching template from Keyfactor") - template, err := kfClient.GetTemplate(templateIdNumber) - if err != nil { - tflog.Error(ctx, "Error fetching template from Keyfactor", map[string]interface{}{ - "error": err, - }) - diags.AddError("Error fetching template from Keyfactor", err.Error()) - return diags - } - - // Check if role is already assigned as an allowed requester for the template, and - var newAllowedRequester []string - for _, name := range template.AllowedRequesters { - if name == roleName { - tflog.Warn(ctx, "Keyfactor security role is already listed as an allowed requester for template.", map[string]interface{}{ - "role": roleName, - "template": template.CommonName, - "name": name, - }) - return diags - } - tflog.Debug(ctx, "Adding role to allowed requesters", map[string]interface{}{ - "role": roleName, - "template": template.CommonName, - }) - newAllowedRequester = append(newAllowedRequester, name) - } - - // If it's not already added, create update context to add role to template. - - newAllowedRequester = append(newAllowedRequester, roleName) - useAllowedRequesters := false - if len(newAllowedRequester) > 0 { - useAllowedRequesters = true - } - // Fill required fields with information retrieved from the get request above - tflog.Debug(ctx, "Creating update context to add role to template") - updateContext := &api.UpdateTemplateArg{ - Id: template.Id, - CommonName: template.CommonName, - TemplateName: template.TemplateName, - Oid: template.Oid, - KeySize: template.KeySize, - ForestRoot: template.ForestRoot, - UseAllowedRequesters: boolToPointer(useAllowedRequesters), - AllowedRequesters: &newAllowedRequester, - FriendlyName: stringToPointer(template.FriendlyName), - AllowedEnrollmentTypes: intToPointer(template.AllowedEnrollmentTypes), - KeyRetention: stringToPointer(template.KeyRetention), - RFCEnforcement: boolToPointer(template.RFCEnforcement), - } - - tflog.Trace(ctx, "Updating template in Keyfactor with context:", map[string]interface{}{ - "context": updateContext, - }) - _, err = kfClient.UpdateTemplate(updateContext) - if err != nil { - tflog.Error(ctx, "Error updating template in Keyfactor", map[string]interface{}{ - "error": err, - }) - diags.AddError("Error updating template in Keyfactor", err.Error()) - return diags - } - - return diags -} - -/* - * The resourceTemplateAttachRoleRead function is responsible for reading a Keyfactor security role. - */ -func removeRoleFromTemplate(ctx context.Context, kfClient *api.Client, roleName string, templateId int) diag.Diagnostics { - var diags diag.Diagnostics - ctx = tflog.SetField(ctx, "template_id", templateId) - ctx = tflog.SetField(ctx, "role_name", roleName) - tflog.Debug(ctx, "Removing Keyfactor role with ID from template") - // First get info about template from Keyfactor - template, err := kfClient.GetTemplate(templateId) - if err != nil { - tflog.Error(ctx, "Error fetching template from Keyfactor", map[string]interface{}{ - "error": err, - }) - diags.AddError("Error fetching template from Keyfactor", err.Error()) - } - - // Rebuild allowed requester list without roleName - var newAllowedRequester []string - tflog.Debug(ctx, "Rebuild allowed requester list without roleName") - for _, name := range template.AllowedRequesters { - if name != roleName { - tflog.Trace(ctx, "Adding role to allowed requesters", map[string]interface{}{ - "allowed_requester": newAllowedRequester, - "name": name, - }) - newAllowedRequester = append(newAllowedRequester, name) - } - } - - useAllowedRequesters := false - if len(newAllowedRequester) > 0 { - useAllowedRequesters = true - } - // Fill required fields with information retrieved from the get request above - updateContext := &api.UpdateTemplateArg{ - Id: template.Id, - CommonName: template.CommonName, - TemplateName: template.TemplateName, - Oid: template.Oid, - KeySize: template.KeySize, - ForestRoot: template.ForestRoot, - UseAllowedRequesters: boolToPointer(useAllowedRequesters), - AllowedRequesters: &newAllowedRequester, - FriendlyName: stringToPointer(template.FriendlyName), - AllowedEnrollmentTypes: intToPointer(template.AllowedEnrollmentTypes), - KeyRetention: stringToPointer(template.KeyRetention), - RFCEnforcement: boolToPointer(template.RFCEnforcement), - } - - tflog.Trace(ctx, "Updating template in Keyfactor with context:", map[string]interface{}{ - "context": updateContext, - }) - _, err = kfClient.UpdateTemplate(updateContext) - if err != nil { - tflog.Error(ctx, "Error updating template in Keyfactor", map[string]interface{}{ - "error": err, - }) - diags.AddError("Error updating template in Keyfactor", err.Error()) - return diags - } - return diags -} - -/* - * The resourceTemplateAttachRoleDelete function is responsible for deleting a Keyfactor security role. - */ -func flattenAttachRoleSchema(ctx context.Context, roleName string, templateIds []interface{}) map[string]interface{} { - tflog.Debug(ctx, "Flattening Keyfactor role resource schema.") - data := make(map[string]interface{}) - - data["role_name"] = roleName - - tempSet := schema.NewSet(schema.HashInt, templateIds) - data["template_ids"] = tempSet - - return data -} - -/* - * The resourceTemplateAttachRoleDelete function is responsible for deleting a Keyfactor security role. - */ -func findTemplateRoleAttachments(ctx context.Context, kfClient *api.Client, roleName string) (diag.Diagnostics, []interface{}) { - // Goal here is to find every template that the role is listed as an allowed requester. First thing that needs - // to happen is retrieve a complete list of all certificate templates. - - var diags diag.Diagnostics - tflog.Debug(ctx, "Fetching all templates from Keyfactor") - templates, err := kfClient.GetTemplates() - if err != nil { - diags.AddError("Error fetching templates from Keyfactor", err.Error()) - return diags, make([]interface{}, 0) - } - - var templateRoleAttachmentList []interface{} - - for _, template := range templates { - // We only want to check the allowed requester list if UseAllowedRequesters is true - if template.UseAllowedRequesters { - for _, role := range template.AllowedRequesters { - if role == roleName { - templateRoleAttachmentList = append(templateRoleAttachmentList, template.Id) - } - } - } - } - - return diags, templateRoleAttachmentList -} diff --git a/keyfactor/resource_keyfactor_template_role_binding_test.go b/keyfactor/resource_keyfactor_template_role_binding_test.go deleted file mode 100644 index ea5f7336..00000000 --- a/keyfactor/resource_keyfactor_template_role_binding_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package keyfactor - -import ( - "encoding/json" - "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "os" - "testing" -) - -type roleBindingTestCase struct { - roleName string - templates []string - templatesStr string - resourceName string -} - -func TestAccKeyfactorTemplateRoleBindingResource(t *testing.T) { - - r := roleBindingTestCase{ - roleName: os.Getenv("KEYFACTOR_TEMPLATE_ROLE_BINDING_ROLE_NAME"), - templates: []string{ - os.Getenv("KEYFACTOR_TEMPLATE_ROLE_BINDING_TEMPLATE_NAME1"), - }, - resourceName: "keyfactor_template_role_binding.terraform_test", - } - pStr, _ := json.Marshal(r.templates) - r.templatesStr = string(pStr) - - // Update to multiple roleBindings test - r2 := r - additionalTemplates := []string{ - os.Getenv("KEYFACTOR_TEMPLATE_ROLE_BINDING_TEMPLATE_NAME2"), - os.Getenv("KEYFACTOR_TEMPLATE_ROLE_BINDING_TEMPLATE_NAME3"), - } - r2.templates = append(r2.templates, additionalTemplates...) - r2Str, _ := json.Marshal(r2.templates) - r2.templatesStr = string(r2Str) - - // Update to no roleBindings test - r3 := r2 - r3.templates = []string{} - r3Str, _ := json.Marshal(r3.templates) - r3.templatesStr = string(r3Str) - - // Testing Role - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - // Create and Read testing - { - //ResourceName: "", - //PreConfig: nil, - //Taint: nil, - Config: testAccKeyfactorTemplateRoleBindingResourceConfig(r), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(r.resourceName, "id"), - resource.TestCheckResourceAttrSet(r.resourceName, "role_name"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r.resourceName, "template_short_names.0"), // TODO: Check specific value - - ), - //Destroy: false, - //ExpectNonEmptyPlan: false, - //ExpectError: nil, - //PlanOnly: false, - //PreventDiskCleanup: false, - //PreventPostDestroyRefresh: false, - //SkipFunc: nil, - //ImportState: false, - //ImportStateId: "", - //ImportStateIdPrefix: "", - //ImportStateIdFunc: nil, - //ImportStateCheck: nil, - //ImportStateVerify: false, - //ImportStateVerifyIgnore: nil, - //ProviderFactories: nil, - //ProtoV5ProviderFactories: nil, - //ProtoV6ProviderFactories: nil, - //ExternalProviders: nil, - }, - // ImportState testing - //{ - // ResourceName: "scaffolding_example.test", - // ImportState: false, - // ImportStateVerify: false, - // // This is not normally necessary, but is here because this - // // example code does not have an actual upstream service. - // // Once the Read method is able to refresh information from - // // the upstream service, this can be removed. - // ImportStateVerifyIgnore: []string{"configurable_attribute"}, - //}, - // Update and Read testing - { - Config: testAccKeyfactorTemplateRoleBindingResourceConfig(r2), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(r2.resourceName, "id"), - resource.TestCheckResourceAttrSet(r2.resourceName, "role_name"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r2.resourceName, "template_short_names.0"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r2.resourceName, "template_short_names.1"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r2.resourceName, "template_short_names.2"), // TODO: Check specific value - ), - }, - { - Config: testAccKeyfactorTemplateRoleBindingResourceConfig(r3), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttrSet(r3.resourceName, "id"), - resource.TestCheckResourceAttrSet(r3.resourceName, "role_name"), // TODO: Check specific value - resource.TestCheckResourceAttrSet(r3.resourceName, "template_short_names.#"), - ), - }, - // Delete testing automatically occurs in TestCase - }, - }) -} - -func testAccKeyfactorTemplateRoleBindingResourceConfig(t roleBindingTestCase) string { - output := fmt.Sprintf(` -resource "keyfactor_template_role_binding" "terraform_test" { - role_name = "%s" - template_short_names = %s -} -`, t.roleName, t.templatesStr) - return output -} diff --git a/keyfactor/version.go b/keyfactor/version.go deleted file mode 100644 index 395bd597..00000000 --- a/keyfactor/version.go +++ /dev/null @@ -1,3 +0,0 @@ -package keyfactor - -const VERSION = "1.3.0" diff --git a/local_dev_vendor.sh b/local_dev_vendor.sh index 86fea356..71ccda71 100755 --- a/local_dev_vendor.sh +++ b/local_dev_vendor.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash make vendor +mkdir -p vendor/github.com/Keyfactor cd vendor/github.com/Keyfactor rm -rf keyfactor-go-client -ln -s $GO_PATH/keyfactor-go-client . +rm -rf keyfactor-go-client-sdk +ln -s /Users/sbailey/GolandProjects/keyfactor-go-client . +ln -s /Users/sbailey/GolandProjects/keyfactor-go-client-sdk . cd ../../.. \ No newline at end of file diff --git a/main.go b/main.go index d1384738..2ad49326 100644 --- a/main.go +++ b/main.go @@ -3,8 +3,9 @@ package main import ( "context" "flag" + "github.com/getsentry/sentry-go" "github.com/hashicorp/terraform-plugin-framework/providerserver" - "github.com/keyfactor-pub/terraform-provider-keyfactor/keyfactor" + v3 "github.com/keyfactor-pub/terraform-provider-keyfactor/internal/provider/v3/command" "log" "os" ) @@ -25,25 +26,45 @@ func main() { version := os.Getenv("GITHUB_REF_NAME") if version == "" { - version = keyfactor.VERSION + version = v3.VERSION } flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") flag.Parse() + if !debug { + envDebug := os.Getenv("DEBUG") + if envDebug != "" && envDebug != "false" { + debug = true + } + } + address := "github.com/keyfactor-pub/keyfactor" if debug { - address = "keyfactor.com/keyfactor/keyfactor" + address = "keyfactor.com/command/v3" + configSentry() } opts := providerserver.ServeOpts{ - Address: address, - Debug: debug, - ProtocolVersion: 6, + Address: address, + Debug: debug, } - err := providerserver.Serve(context.Background(), keyfactor.New, opts) + err := providerserver.Serve(context.Background(), v3.New(version), opts) if err != nil { log.Fatal(err.Error()) } } + +func configSentry() { + err := sentry.Init(sentry.ClientOptions{ + Dsn: "https://814b0846082d547816b62ebab2b59aa0@o4505352704360448.ingest.sentry.io/4506395161067520", + // Set TracesSampleRate to 1.0 to capture 100% + // of transactions for performance monitoring. + // We recommend adjusting this value in production, + TracesSampleRate: 1.0, + }) + if err != nil { + log.Fatalf("sentry.Init: %s", err) + } +} diff --git a/readme_source.md b/readme_source.md index cb155212..c8301968 100644 --- a/readme_source.md +++ b/readme_source.md @@ -12,7 +12,7 @@ ### Mac OS/Linux ```bash git clone https://github.com/Keyfactor/terraform-provider-keyfactor.git -cd terraform-provider-keyfactor +cd terraform-provider-v2 make install ``` diff --git a/repo/action_secrets.tf b/repo/action_secrets.tf index 7692c896..f114b258 100644 --- a/repo/action_secrets.tf +++ b/repo/action_secrets.tf @@ -42,3 +42,33 @@ resource "github_actions_secret" "test_store_password" { secret_name = "TEST_CERTIFICATE_STORE_PASS" plaintext_value = var.test_store_password } + +resource "github_actions_secret" "test_certificate_id" { + repository = data.github_repository.terraform_provider_keyfactor.name + secret_name = "TEST_CERTIFICATE_ID" + plaintext_value = var.test_cert_id +} + +resource "github_actions_secret" "test_certificate_cn" { + repository = data.github_repository.terraform_provider_keyfactor.name + secret_name = "TEST_CERTIFICATE_CN" + plaintext_value = var.test_cert_cn +} + +resource "github_actions_secret" "test_certificate_thumbprint" { + repository = data.github_repository.terraform_provider_keyfactor.name + secret_name = "TEST_CERTIFICATE_THUMBPRINT" + plaintext_value = var.test_cert_thumbprint +} + +resource "github_actions_secret" "test_agent_id" { + repository = data.github_repository.terraform_provider_keyfactor.name + secret_name = "TEST_AGENT_ID" + plaintext_value = var.test_agent_id +} + +resource "github_actions_secret" "test_agent_name" { + repository = data.github_repository.terraform_provider_keyfactor.name + secret_name = "TEST_AGENT_CLIENT_MACHINE_NAME" + plaintext_value = var.test_agent_client_machine_name +} \ No newline at end of file diff --git a/repo/variables.tf b/repo/variables.tf index 4f50ea52..e39c5a02 100644 --- a/repo/variables.tf +++ b/repo/variables.tf @@ -34,6 +34,17 @@ variable "test_cert_id" { default = "1" } +variable "test_cert_cn" { + description = "The Keyfactor Command ID of a certificate to use for testing. Note: the certificate must exist." + type = string + default = "Terraform Provider Test Certificate" +} + +variable "test_cert_thumbprint" { + description = "The Keyfactor Command ID of a certificate to use for testing. Note: the certificate must exist." + type = string +} + variable "test_cert_password" { description = "The password to use when creating test certificate(s)." type = string @@ -135,4 +146,14 @@ variable "test_deploy_cert_storeid2" { variable "test_certificate_template_name" { type = string description = "Name of an existing template to use for testing. Note: the template must exist in Keyfactor, be usable by the CA specificed in `test_certificate_ca_name` and the user specified in `test_username`. " +} + +variable "test_agent_client_machine_name" { + type = string + description = "Name of an existing agent to use for testing. Note: the agent must exist in Keyfactor." +} + +variable "test_agent_id" { + type = string + description = "ID of an existing agent to use for testing. Note: the agent must exist in Keyfactor." } \ No newline at end of file diff --git a/tools/tools.go b/tools/tools.go new file mode 100644 index 00000000..2c4f8fba --- /dev/null +++ b/tools/tools.go @@ -0,0 +1,8 @@ +//go:build tools + +package tools + +import ( + // Documentation generation + _ "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs" +)