diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 161a6d0922932bda37ea8ac5087492a2bea916e8..f050bebe91296e19e0bd0c5447c1f1fa0fa3d1d1 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -51,7 +51,6 @@ jobs:
     name: Terraform Provider Acceptance Tests
     needs: build
     runs-on: ubuntu-latest
-    timeout-minutes: 15
     strategy:
       fail-fast: false
       matrix:
@@ -75,8 +74,8 @@ jobs:
           SMALLSTEP_API_URL: https://gateway.smallstep.com/api
           SMALLSTEP_API_TOKEN: ${{ secrets.SMALLSTEP_API_TOKEN }}
         run: |
-          go test -v -cover ./internal/provider/
-        timeout-minutes: 10
+          go test -v -cover ./internal/provider/ -timeout 20m
+        timeout-minutes: 20
   sweep:
     runs-on: ubuntu-latest
     needs: test
diff --git a/Makefile b/Makefile
index e81458c20855d6a707499424d287d9b3c4d6282e..27b15a797e8ce24a92d383999528738f6139f830 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@ default: testacc
 # Run acceptance tests
 .PHONY: testacc
 testacc:
-	TF_ACC_LOG=INFO TF_ACC=1 go test ./... -v -timeout 10m
+	TF_ACC_LOG=INFO TF_ACC=1 go test ./... -v -timeout 20m
 
 sweep:
 	TF_ACC_LOG=INFO TF_ACC=1 go test ./... -v -timeout 10m -sweep="1"
diff --git a/docs/data-sources/agent_configuration.md b/docs/data-sources/agent_configuration.md
new file mode 100644
index 0000000000000000000000000000000000000000..84bd1087d550a4e14b39d456be67b3ff9d4f875e
--- /dev/null
+++ b/docs/data-sources/agent_configuration.md
@@ -0,0 +1,35 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "smallstep_agent_configuration Data Source - terraform-provider-smallstep"
+subcategory: ""
+description: |-
+  The agent configuration describes the attestation authority used by the agent to grant workload certificates.
+---
+
+# smallstep_agent_configuration (Data Source)
+
+The agent configuration describes the attestation authority used by the agent to grant workload certificates.
+
+## Example Usage
+
+```terraform
+data "smallstep_agent_configuration" "agent1" {
+  id = "0496154c-ea90-4642-a2b9-96e76e69d219"
+}
+```
+
+<!-- schema generated by tfplugindocs -->
+## Schema
+
+### Required
+
+- `id` (String) A UUID identifying this agent configuration. Generated server-side on creation.
+
+### Read-Only
+
+- `attestation_slug` (String) The slug of the attestation authority the agent connects to to get a certificate.
+- `authority_id` (String) UUID identifying the authority the agent uses to generate endpoint certificates.
+- `name` (String) The name of this agent configuration.
+- `provisioner_name` (String) The name of the provisioner on the authority the agent uses to generate endpoint certificates.
+
+
diff --git a/docs/data-sources/endpoint_configuration.md b/docs/data-sources/endpoint_configuration.md
new file mode 100644
index 0000000000000000000000000000000000000000..f61b33bc8717d6fe51e919c7b1a8373d1b9748a4
--- /dev/null
+++ b/docs/data-sources/endpoint_configuration.md
@@ -0,0 +1,104 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "smallstep_endpoint_configuration Data Source - terraform-provider-smallstep"
+subcategory: ""
+description: |-
+  Configuration for a managed endpoint
+---
+
+# smallstep_endpoint_configuration (Data Source)
+
+Configuration for a managed endpoint
+
+## Example Usage
+
+```terraform
+data "smallstep_endpoint_configuration" "ep1" {
+  id = "2c495e72-cbcf-440c-aa34-7736d76645e7"
+}
+```
+
+<!-- schema generated by tfplugindocs -->
+## Schema
+
+### Required
+
+- `id` (String) A UUID identifying this endpoint configuration. Generated server-side when the endpoint configuration is created.
+
+### Read-Only
+
+- `authority_id` (String) UUID identifying the authority that will issue certificates for the endpoint.
+- `certificate_info` (Attributes) Details on a managed certificate (see [below for nested schema](#nestedatt--certificate_info))
+- `hooks` (Attributes) The collection of commands to run when a certificate for a managed endpoint is signed or renewed. (see [below for nested schema](#nestedatt--hooks))
+- `key_info` (Attributes) The attributes of the cryptographic key. (see [below for nested schema](#nestedatt--key_info))
+- `kind` (String) The kind of endpoint this configuration applies to. Allowed values: `DEVICE` `WORKLOAD` `PEOPLE`
+- `name` (String) The name of the endpoint configuration.
+- `provisioner_name` (String) Name of the provisioner on the authority that will authorize certificates for the endpoint.
+- `reload_info` (Attributes) The properties used to reload a service. (see [below for nested schema](#nestedatt--reload_info))
+
+<a id="nestedatt--certificate_info"></a>
+### Nested Schema for `certificate_info`
+
+Read-Only:
+
+- `crt_file` (String) The filepath where the certificate is to be stored.
+- `duration` (String) The certificate lifetime. Parsed as a [Golang duration](https://pkg.go.dev/time#ParseDuration).
+- `gid` (Number) GID of the files where the certificate is stored.
+- `key_file` (String) The filepath where the key is to be stored.
+- `mode` (Number) Permission bits of the files where the certificate is stored.
+- `root_file` (String) The filepath where the root certificate is to be stored.
+- `type` (String) The type of certificate Allowed values: `X509` `SSH_USER` `SSH_HOST`
+- `uid` (Number) UID of the files where the certificate is stored.
+
+
+<a id="nestedatt--hooks"></a>
+### Nested Schema for `hooks`
+
+Read-Only:
+
+- `renew` (Attributes) A list of commands to run before and after a certificate is granted. (see [below for nested schema](#nestedatt--hooks--renew))
+- `sign` (Attributes) A list of commands to run before and after a certificate is granted. (see [below for nested schema](#nestedatt--hooks--sign))
+
+<a id="nestedatt--hooks--renew"></a>
+### Nested Schema for `hooks.renew`
+
+Read-Only:
+
+- `after` (List of String) List of commands to run after the operation.
+- `before` (List of String) List of commands to run before the operation.
+- `on_error` (List of String) List of commands to run when the operation fails.
+- `shell` (String) The shell to use to execute the commands.
+
+
+<a id="nestedatt--hooks--sign"></a>
+### Nested Schema for `hooks.sign`
+
+Read-Only:
+
+- `after` (List of String) List of commands to run after the operation.
+- `before` (List of String) List of commands to run before the operation.
+- `on_error` (List of String) List of commands to run when the operation fails.
+- `shell` (String) The shell to use to execute the commands.
+
+
+
+<a id="nestedatt--key_info"></a>
+### Nested Schema for `key_info`
+
+Read-Only:
+
+- `format` (String) The format used to encode the private key. For X509 keys the default format is SEC 1 for ECDSA keys, PKCS#1 for RSA keys and PKCS#8 for ED25519 keys. For SSH keys the default format is always the OPENSSH format. Allowed values: `DEFAULT` `PKCS8` `OPENSSH` `DER`
+- `pub_file` (String) A CSR or SSH public key to use instead of generating one.
+- `type` (String) The key type used. The current DEFAULT type is ECDSA_P256. Allowed values: `DEFAULT` `ECDSA_P256` `ECDSA_P384` `ECDSA_P521` `RSA_2048` `RSA_3072` `RSA_4096` `ED25519`
+
+
+<a id="nestedatt--reload_info"></a>
+### Nested Schema for `reload_info`
+
+Read-Only:
+
+- `method` (String) Ways an endpoint can reload a certificate. `AUTOMATIC` means the process is able to detect and reload new certificates automatically. `CUSTOM` means a custom command must be run to trigger the workload to reload the certificates. `SIGNAL` will configure the agent to send a signal to the process in pidFile. Allowed values: `AUTOMATIC` `CUSTOM` `SIGNAL`
+- `pid_file` (String) File that holds the pid of the process to signal. Required when method is SIGNAL.
+- `signal` (Number) The signal to send to a process when a certificate should be reloaded. Required when method is SIGNAL.
+
+
diff --git a/docs/data-sources/managed_configuration.md b/docs/data-sources/managed_configuration.md
new file mode 100644
index 0000000000000000000000000000000000000000..9215d8192bf7e60e8aa5f0bc2e3d28dfe45131d6
--- /dev/null
+++ b/docs/data-sources/managed_configuration.md
@@ -0,0 +1,62 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "smallstep_managed_configuration Data Source - terraform-provider-smallstep"
+subcategory: ""
+description: |-
+  The agent and managed endpoints used in one host.
+---
+
+# smallstep_managed_configuration (Data Source)
+
+The agent and managed endpoints used in one host.
+
+## Example Usage
+
+```terraform
+data "smallstep_managed_configuration" "mc1" {
+  id = "2e891be8-e1c4-4d27-a10c-62be6ccb6a5e"
+}
+```
+
+<!-- schema generated by tfplugindocs -->
+## Schema
+
+### Required
+
+- `id` (String) UUID identifying this managed configuration.
+
+### Read-Only
+
+- `agent_configuration_id` (String) UUID identifying the agent configuration.
+- `host_id` (String) UUID identifying the host this managed configuration is for. Will be generated on server-side if not provided.
+- `managed_endpoints` (Attributes Set) All the information used by an agent to grant a certificate to an endpoint. Exactly one of `x509CertificateData` or `sshCertificateData` must be set and must match the endpoint configuration certificate info type. (see [below for nested schema](#nestedatt--managed_endpoints))
+- `name` (String) The name of this managed configuration.
+
+<a id="nestedatt--managed_endpoints"></a>
+### Nested Schema for `managed_endpoints`
+
+Read-Only:
+
+- `endpoint_configuration_id` (String) UUID identifying the endpoint configuration.
+- `id` (String) UUID identifying this managed endpoint. Generated server-side on creation.
+- `ssh_certificate_data` (Attributes) Contains the information to include when granting an SSH certificate to an endpoint. (see [below for nested schema](#nestedatt--managed_endpoints--ssh_certificate_data))
+- `x509_certificate_data` (Attributes) Contains the information to include when granting an x509 certificate to an endpoint. (see [below for nested schema](#nestedatt--managed_endpoints--x509_certificate_data))
+
+<a id="nestedatt--managed_endpoints--ssh_certificate_data"></a>
+### Nested Schema for `managed_endpoints.ssh_certificate_data`
+
+Read-Only:
+
+- `key_id` (String) The key ID to include in the endpoint certificate.
+- `principals` (Set of String) The principals to include in the endpoint certificate.
+
+
+<a id="nestedatt--managed_endpoints--x509_certificate_data"></a>
+### Nested Schema for `managed_endpoints.x509_certificate_data`
+
+Read-Only:
+
+- `common_name` (String) The Common Name to be used in the subject of the endpoint certificate.
+- `sans` (Set of String) The list of SANs to include in the endpoint certificate.
+
+
diff --git a/docs/resources/agent_configuration.md b/docs/resources/agent_configuration.md
new file mode 100644
index 0000000000000000000000000000000000000000..8ff1728df252330117474bde2759f57f0dd929a9
--- /dev/null
+++ b/docs/resources/agent_configuration.md
@@ -0,0 +1,48 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "smallstep_agent_configuration Resource - terraform-provider-smallstep"
+subcategory: ""
+description: |-
+  The agent configuration describes the attestation authority used by the agent to grant workload certificates.
+---
+
+# smallstep_agent_configuration (Resource)
+
+The agent configuration describes the attestation authority used by the agent to grant workload certificates.
+
+## Example Usage
+
+```terraform
+resource "smallstep_agent_configuration" "agent1" {
+  name             = "Agent1"
+  authority_id     = smallstep_authority.agents_authority.id
+  provisioner_name = smallstep_provisioner.acme_attest.name
+  attestation_slug = smallstep_attestation_authority.aa.slug
+  depends_on       = [smallstep_provisioner.acme_attest]
+}
+```
+
+<!-- schema generated by tfplugindocs -->
+## Schema
+
+### Required
+
+- `authority_id` (String) UUID identifying the authority the agent uses to generate endpoint certificates.
+- `name` (String) The name of this agent configuration.
+- `provisioner_name` (String) The name of the provisioner on the authority the agent uses to generate endpoint certificates.
+
+### Optional
+
+- `attestation_slug` (String) The slug of the attestation authority the agent connects to to get a certificate.
+
+### Read-Only
+
+- `id` (String) A UUID identifying this agent configuration. Generated server-side on creation.
+
+## Import
+
+Import is supported using the following syntax:
+
+```shell
+terraform import smallstep_agent_configuration.ac 7422eaa5-5521-49b8-b7af-aad8f1740165
+```
diff --git a/docs/resources/endpoint_configuration.md b/docs/resources/endpoint_configuration.md
new file mode 100644
index 0000000000000000000000000000000000000000..3a0f297d536e6c2a5a4ee10513d3053313ae9b73
--- /dev/null
+++ b/docs/resources/endpoint_configuration.md
@@ -0,0 +1,178 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "smallstep_endpoint_configuration Resource - terraform-provider-smallstep"
+subcategory: ""
+description: |-
+  Configuration for a managed endpoint
+---
+
+# smallstep_endpoint_configuration (Resource)
+
+Configuration for a managed endpoint
+
+## Example Usage
+
+```terraform
+resource "smallstep_endpoint_configuration" "x509" {
+  name = "My DB"
+  kind = "WORKLOAD"
+
+  authority_id     = smallstep_authority.endpoints.id
+  provisioner_name = smallstep_provisioner.endpoints_x5c.name
+
+  certificate_info = {
+    type      = "X509"
+    duration  = "168h"
+    crt_file  = "db.crt"
+    key_file  = "db.key"
+    root_file = "ca.crt"
+    uid       = 1001
+    gid       = 999
+    mode      = 256
+  }
+
+  hooks = {
+    renew = {
+      shell    = "/bin/sh"
+      before   = ["echo renewing"]
+      after    = ["echo renewed"]
+      on_error = ["echo failed renew"]
+    }
+    sign = {
+      shell    = "/bin/bash"
+      before   = ["echo signing"]
+      after    = ["echo signed"]
+      on_error = ["echo failed sign"]
+    }
+  }
+
+  key_info = {
+    format   = "DEFAULT"
+    type     = "ECDSA_P256"
+    pub_file = "file.csr"
+  }
+
+  reload_info = {
+    method   = "SIGNAL"
+    pid_file = "db.pid"
+    signal   = 1
+  }
+}
+
+resource "smallstep_endpoint_configuration" "ssh" {
+  name             = "SSH"
+  kind             = "PEOPLE"
+  authority_id     = smallstep_authority.endpoints.id
+  provisioner_name = smallstep_provisioner.endpoints_x5c.name
+  certificate_info = {
+    type = "SSH_USER"
+  }
+  key_info = {
+    type   = "RSA_2048"
+    format = "OPENSSH"
+  }
+}
+```
+
+<!-- schema generated by tfplugindocs -->
+## Schema
+
+### Required
+
+- `authority_id` (String) UUID identifying the authority that will issue certificates for the endpoint.
+- `certificate_info` (Attributes) Details on a managed certificate (see [below for nested schema](#nestedatt--certificate_info))
+- `key_info` (Attributes) The attributes of the cryptographic key. (see [below for nested schema](#nestedatt--key_info))
+- `kind` (String) The kind of endpoint this configuration applies to. Allowed values: `DEVICE` `WORKLOAD` `PEOPLE`
+- `name` (String) The name of the endpoint configuration.
+- `provisioner_name` (String) Name of the provisioner on the authority that will authorize certificates for the endpoint.
+
+### Optional
+
+- `hooks` (Attributes) The collection of commands to run when a certificate for a managed endpoint is signed or renewed. (see [below for nested schema](#nestedatt--hooks))
+- `reload_info` (Attributes) The properties used to reload a service. (see [below for nested schema](#nestedatt--reload_info))
+
+### Read-Only
+
+- `id` (String) A UUID identifying this endpoint configuration. Generated server-side when the endpoint configuration is created.
+
+<a id="nestedatt--certificate_info"></a>
+### Nested Schema for `certificate_info`
+
+Required:
+
+- `type` (String) The type of certificate Allowed values: `X509` `SSH_USER` `SSH_HOST`
+
+Optional:
+
+- `crt_file` (String) The filepath where the certificate is to be stored.
+- `duration` (String) The certificate lifetime. Parsed as a [Golang duration](https://pkg.go.dev/time#ParseDuration).
+- `gid` (Number) GID of the files where the certificate is stored.
+- `key_file` (String) The filepath where the key is to be stored.
+- `mode` (Number) Permission bits of the files where the certificate is stored.
+- `root_file` (String) The filepath where the root certificate is to be stored.
+- `uid` (Number) UID of the files where the certificate is stored.
+
+
+<a id="nestedatt--key_info"></a>
+### Nested Schema for `key_info`
+
+Required:
+
+- `format` (String) The format used to encode the private key. For X509 keys the default format is SEC 1 for ECDSA keys, PKCS#1 for RSA keys and PKCS#8 for ED25519 keys. For SSH keys the default format is always the OPENSSH format. Allowed values: `DEFAULT` `PKCS8` `OPENSSH` `DER`
+- `type` (String) The key type used. The current DEFAULT type is ECDSA_P256. Allowed values: `DEFAULT` `ECDSA_P256` `ECDSA_P384` `ECDSA_P521` `RSA_2048` `RSA_3072` `RSA_4096` `ED25519`
+
+Optional:
+
+- `pub_file` (String) A CSR or SSH public key to use instead of generating one.
+
+
+<a id="nestedatt--hooks"></a>
+### Nested Schema for `hooks`
+
+Optional:
+
+- `renew` (Attributes) A list of commands to run before and after a certificate is granted. (see [below for nested schema](#nestedatt--hooks--renew))
+- `sign` (Attributes) A list of commands to run before and after a certificate is granted. (see [below for nested schema](#nestedatt--hooks--sign))
+
+<a id="nestedatt--hooks--renew"></a>
+### Nested Schema for `hooks.renew`
+
+Optional:
+
+- `after` (List of String) List of commands to run after the operation.
+- `before` (List of String) List of commands to run before the operation.
+- `on_error` (List of String) List of commands to run when the operation fails.
+- `shell` (String) The shell to use to execute the commands.
+
+
+<a id="nestedatt--hooks--sign"></a>
+### Nested Schema for `hooks.sign`
+
+Optional:
+
+- `after` (List of String) List of commands to run after the operation.
+- `before` (List of String) List of commands to run before the operation.
+- `on_error` (List of String) List of commands to run when the operation fails.
+- `shell` (String) The shell to use to execute the commands.
+
+
+
+<a id="nestedatt--reload_info"></a>
+### Nested Schema for `reload_info`
+
+Required:
+
+- `method` (String) Ways an endpoint can reload a certificate. `AUTOMATIC` means the process is able to detect and reload new certificates automatically. `CUSTOM` means a custom command must be run to trigger the workload to reload the certificates. `SIGNAL` will configure the agent to send a signal to the process in pidFile. Allowed values: `AUTOMATIC` `CUSTOM` `SIGNAL`
+
+Optional:
+
+- `pid_file` (String) File that holds the pid of the process to signal. Required when method is SIGNAL.
+- `signal` (Number) The signal to send to a process when a certificate should be reloaded. Required when method is SIGNAL.
+
+## Import
+
+Import is supported using the following syntax:
+
+```shell
+terraform import smallstep_endpoint_configuration.ep1 7bcde9eb-daf8-4be7-9d82-21da3ff60e81
+```
diff --git a/docs/resources/managed_configuration.md b/docs/resources/managed_configuration.md
new file mode 100644
index 0000000000000000000000000000000000000000..1643366ecd9670024c472f0ad9b710a314f2c587
--- /dev/null
+++ b/docs/resources/managed_configuration.md
@@ -0,0 +1,100 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "smallstep_managed_configuration Resource - terraform-provider-smallstep"
+subcategory: ""
+description: |-
+  The agent and managed endpoints used in one host.
+---
+
+# smallstep_managed_configuration (Resource)
+
+The agent and managed endpoints used in one host.
+
+## Example Usage
+
+```terraform
+resource "smallstep_managed_configuration" "mc" {
+  agent_configuration_id = smallstep_agent_configuration.agent1.id
+  host_id                = "9cdaf513-3296-4037-bd9b-d0634f51cd79"
+  name                   = "DB Server"
+  managed_endpoints = [
+    {
+      endpoint_configuration_id = smallstep_endpoint_configuration.x509.id
+      x509_certificate_data = {
+        common_name = "db"
+        sans = [
+          "db",
+          "db.default",
+          "db.default.svc",
+          "db.defaulst.svc.cluster.local",
+        ]
+      }
+    },
+    {
+      endpoint_configuration_id = smallstep_endpoint_configuration.ssh.id
+      ssh_certificate_data = {
+        key_id     = "abc"
+        principals = ["ops", "eng"]
+      }
+    },
+  ]
+}
+```
+
+<!-- schema generated by tfplugindocs -->
+## Schema
+
+### Required
+
+- `agent_configuration_id` (String) UUID identifying the agent configuration.
+- `managed_endpoints` (Attributes Set) All the information used by an agent to grant a certificate to an endpoint. Exactly one of `x509CertificateData` or `sshCertificateData` must be set and must match the endpoint configuration certificate info type. (see [below for nested schema](#nestedatt--managed_endpoints))
+- `name` (String) The name of this managed configuration.
+
+### Optional
+
+- `host_id` (String) UUID identifying the host this managed configuration is for. Will be generated on server-side if not provided.
+
+### Read-Only
+
+- `id` (String) UUID identifying this managed configuration.
+
+<a id="nestedatt--managed_endpoints"></a>
+### Nested Schema for `managed_endpoints`
+
+Required:
+
+- `endpoint_configuration_id` (String) UUID identifying the endpoint configuration.
+
+Optional:
+
+- `ssh_certificate_data` (Attributes) Contains the information to include when granting an SSH certificate to an endpoint. (see [below for nested schema](#nestedatt--managed_endpoints--ssh_certificate_data))
+- `x509_certificate_data` (Attributes) Contains the information to include when granting an x509 certificate to an endpoint. (see [below for nested schema](#nestedatt--managed_endpoints--x509_certificate_data))
+
+Read-Only:
+
+- `id` (String) UUID identifying this managed endpoint. Generated server-side on creation.
+
+<a id="nestedatt--managed_endpoints--ssh_certificate_data"></a>
+### Nested Schema for `managed_endpoints.ssh_certificate_data`
+
+Required:
+
+- `key_id` (String) The key ID to include in the endpoint certificate.
+- `principals` (Set of String) The principals to include in the endpoint certificate.
+
+
+<a id="nestedatt--managed_endpoints--x509_certificate_data"></a>
+### Nested Schema for `managed_endpoints.x509_certificate_data`
+
+Required:
+
+- `common_name` (String) The Common Name to be used in the subject of the endpoint certificate.
+- `sans` (Set of String) The list of SANs to include in the endpoint certificate.
+
+## Import
+
+Import is supported using the following syntax:
+
+```shell
+terraform import smallstep_managed_configuration.mc 411009f5-b30d-4c16-818f-cefcc71d86b4
+```
diff --git a/e2e/managed.tf b/e2e/managed.tf
index 83ff4b43b8f7d2e5f45a2214c9af6cfc4b376665..1505d82d93ced1a5adf5c6e58fd74968363593bc 100644
--- a/e2e/managed.tf
+++ b/e2e/managed.tf
@@ -62,3 +62,98 @@ resource "smallstep_provisioner_webhook" "devices" {
   collection_slug = smallstep_collection.tpms.slug
   depends_on      = [smallstep_collection.tpms]
 }
+
+resource "smallstep_agent_configuration" "agent1" {
+  authority_id     = smallstep_authority.agents.id
+  provisioner_name = smallstep_provisioner.agents.name
+  name             = "Agent1"
+  attestation_slug = smallstep_attestation_authority.aa.slug
+  depends_on       = [smallstep_provisioner.agents]
+}
+
+resource "smallstep_endpoint_configuration" "ep_x509" {
+  name = "My DB"
+  kind = "WORKLOAD"
+
+  authority_id     = smallstep_authority.endpoints.id
+  provisioner_name = smallstep_provisioner.endpoints.name
+
+  certificate_info = {
+    type      = "X509"
+    duration  = "168h"
+    crt_file  = "db.crt"
+    key_file  = "db.key"
+    root_file = "ca.crt"
+    uid       = 1001
+    gid       = 999
+    mode      = 256
+  }
+
+  hooks = {
+    renew = {
+      shell    = "/bin/sh"
+      before   = ["echo renewing"]
+      after    = ["echo renewed"]
+      on_error = ["echo failed renew"]
+    }
+    sign = {
+      shell    = "/bin/bash"
+      before   = ["echo signing"]
+      after    = ["echo signed"]
+      on_error = ["echo failed sign"]
+    }
+  }
+
+  key_info = {
+    format   = "DEFAULT"
+    type     = "ECDSA_P256"
+    pub_file = "file.csr"
+  }
+
+  reload_info = {
+    method   = "SIGNAL"
+    pid_file = "db.pid"
+    signal   = 1
+  }
+}
+
+resource "smallstep_endpoint_configuration" "ep_ssh" {
+  name             = "SSH"
+  kind             = "PEOPLE"
+  authority_id     = smallstep_authority.agents.id
+  provisioner_name = smallstep_provisioner.agents.name
+  certificate_info = {
+    type = "SSH_USER"
+  }
+  key_info = {
+    type   = "RSA_2048"
+    format = "OPENSSH"
+  }
+}
+
+resource "smallstep_managed_configuration" "mc" {
+  agent_configuration_id = smallstep_agent_configuration.agent1.id
+  host_id                = "9cdaf513-3296-4037-bd9b-d0634f51cd79"
+  name                   = "DB Server"
+  managed_endpoints = [
+    {
+      endpoint_configuration_id = smallstep_endpoint_configuration.ep_x509.id
+      x509_certificate_data = {
+        common_name = "db"
+        sans = [
+          "db",
+          "db.default",
+          "db.default.svc",
+          "db.defaulst.svc.cluster.local",
+        ]
+      }
+    },
+    {
+      endpoint_configuration_id = smallstep_endpoint_configuration.ep_ssh.id
+      ssh_certificate_data = {
+        key_id     = "abc"
+        principals = ["ops"]
+      }
+    },
+  ]
+}
diff --git a/e2e/output.tf b/e2e/output.tf
new file mode 100644
index 0000000000000000000000000000000000000000..b833485a4db9b775b430fe6051ea19c5e599387f
--- /dev/null
+++ b/e2e/output.tf
@@ -0,0 +1,15 @@
+output "agent_configuration_id" {
+  value = smallstep_agent_configuration.agent1.id
+}
+
+output "ep_x509_id" {
+  value = smallstep_endpoint_configuration.ep_x509.id
+}
+
+output "ep_ssh_id" {
+  value = smallstep_endpoint_configuration.ep_ssh.id
+}
+
+output "managed_configuration_id" {
+  value = smallstep_managed_configuration.mc.id
+}
diff --git a/examples/data-sources/smallstep_agent_configuration/data-source.tf b/examples/data-sources/smallstep_agent_configuration/data-source.tf
new file mode 100644
index 0000000000000000000000000000000000000000..594dedf57884fa1e81eb678b663e800e5ef836ad
--- /dev/null
+++ b/examples/data-sources/smallstep_agent_configuration/data-source.tf
@@ -0,0 +1,4 @@
+
+data "smallstep_agent_configuration" "agent1" {
+  id = "0496154c-ea90-4642-a2b9-96e76e69d219"
+}
diff --git a/examples/data-sources/smallstep_endpoint_configuration/data-source.tf b/examples/data-sources/smallstep_endpoint_configuration/data-source.tf
new file mode 100644
index 0000000000000000000000000000000000000000..348a8c1c12e9e3a4f2298456ed8b51ecbc97796f
--- /dev/null
+++ b/examples/data-sources/smallstep_endpoint_configuration/data-source.tf
@@ -0,0 +1,4 @@
+
+data "smallstep_endpoint_configuration" "ep1" {
+  id = "2c495e72-cbcf-440c-aa34-7736d76645e7"
+}
diff --git a/examples/data-sources/smallstep_managed_configuration/data-source.tf b/examples/data-sources/smallstep_managed_configuration/data-source.tf
new file mode 100644
index 0000000000000000000000000000000000000000..cdaa4bd906c93b653e790b98dde34b8876351028
--- /dev/null
+++ b/examples/data-sources/smallstep_managed_configuration/data-source.tf
@@ -0,0 +1,4 @@
+
+data "smallstep_managed_configuration" "mc1" {
+  id = "2e891be8-e1c4-4d27-a10c-62be6ccb6a5e"
+}
diff --git a/examples/data-sources/smallstep_managed_configuration/smallstep_managed_configuration/data-source.tf b/examples/data-sources/smallstep_managed_configuration/smallstep_managed_configuration/data-source.tf
new file mode 100644
index 0000000000000000000000000000000000000000..cdaa4bd906c93b653e790b98dde34b8876351028
--- /dev/null
+++ b/examples/data-sources/smallstep_managed_configuration/smallstep_managed_configuration/data-source.tf
@@ -0,0 +1,4 @@
+
+data "smallstep_managed_configuration" "mc1" {
+  id = "2e891be8-e1c4-4d27-a10c-62be6ccb6a5e"
+}
diff --git a/examples/resources/smallstep_agent_configuration/import.sh b/examples/resources/smallstep_agent_configuration/import.sh
new file mode 100644
index 0000000000000000000000000000000000000000..24c82c0e79f449cf93bd1aadfa9f589aac25e757
--- /dev/null
+++ b/examples/resources/smallstep_agent_configuration/import.sh
@@ -0,0 +1 @@
+terraform import smallstep_agent_configuration.ac 7422eaa5-5521-49b8-b7af-aad8f1740165
diff --git a/examples/resources/smallstep_agent_configuration/resource.tf b/examples/resources/smallstep_agent_configuration/resource.tf
new file mode 100644
index 0000000000000000000000000000000000000000..eccfe6971c41a62172be0c07ec2c787669ec1b30
--- /dev/null
+++ b/examples/resources/smallstep_agent_configuration/resource.tf
@@ -0,0 +1,8 @@
+
+resource "smallstep_agent_configuration" "agent1" {
+  name             = "Agent1"
+  authority_id     = smallstep_authority.agents_authority.id
+  provisioner_name = smallstep_provisioner.acme_attest.name
+  attestation_slug = smallstep_attestation_authority.aa.slug
+  depends_on       = [smallstep_provisioner.acme_attest]
+}
diff --git a/examples/resources/smallstep_endpoint_configuration/import.sh b/examples/resources/smallstep_endpoint_configuration/import.sh
new file mode 100644
index 0000000000000000000000000000000000000000..4367f43546d2634eea2c10a3ced83844e158ce54
--- /dev/null
+++ b/examples/resources/smallstep_endpoint_configuration/import.sh
@@ -0,0 +1 @@
+terraform import smallstep_endpoint_configuration.ep1 7bcde9eb-daf8-4be7-9d82-21da3ff60e81
diff --git a/examples/resources/smallstep_endpoint_configuration/resource.tf b/examples/resources/smallstep_endpoint_configuration/resource.tf
new file mode 100644
index 0000000000000000000000000000000000000000..4c711a3c2b617d2a7e1fa396aa6043e429662fbf
--- /dev/null
+++ b/examples/resources/smallstep_endpoint_configuration/resource.tf
@@ -0,0 +1,60 @@
+
+resource "smallstep_endpoint_configuration" "x509" {
+  name = "My DB"
+  kind = "WORKLOAD"
+
+  authority_id     = smallstep_authority.endpoints.id
+  provisioner_name = smallstep_provisioner.endpoints_x5c.name
+
+  certificate_info = {
+    type      = "X509"
+    duration  = "168h"
+    crt_file  = "db.crt"
+    key_file  = "db.key"
+    root_file = "ca.crt"
+    uid       = 1001
+    gid       = 999
+    mode      = 256
+  }
+
+  hooks = {
+    renew = {
+      shell    = "/bin/sh"
+      before   = ["echo renewing"]
+      after    = ["echo renewed"]
+      on_error = ["echo failed renew"]
+    }
+    sign = {
+      shell    = "/bin/bash"
+      before   = ["echo signing"]
+      after    = ["echo signed"]
+      on_error = ["echo failed sign"]
+    }
+  }
+
+  key_info = {
+    format   = "DEFAULT"
+    type     = "ECDSA_P256"
+    pub_file = "file.csr"
+  }
+
+  reload_info = {
+    method   = "SIGNAL"
+    pid_file = "db.pid"
+    signal   = 1
+  }
+}
+
+resource "smallstep_endpoint_configuration" "ssh" {
+  name             = "SSH"
+  kind             = "PEOPLE"
+  authority_id     = smallstep_authority.endpoints.id
+  provisioner_name = smallstep_provisioner.endpoints_x5c.name
+  certificate_info = {
+    type = "SSH_USER"
+  }
+  key_info = {
+    type   = "RSA_2048"
+    format = "OPENSSH"
+  }
+}
diff --git a/examples/resources/smallstep_managed_configuration/import.sh b/examples/resources/smallstep_managed_configuration/import.sh
new file mode 100644
index 0000000000000000000000000000000000000000..bf0d69e60a802891d21237800926b820d11fbf1f
--- /dev/null
+++ b/examples/resources/smallstep_managed_configuration/import.sh
@@ -0,0 +1 @@
+terraform import smallstep_managed_configuration.mc 411009f5-b30d-4c16-818f-cefcc71d86b4
diff --git a/examples/resources/smallstep_managed_configuration/resource.tf b/examples/resources/smallstep_managed_configuration/resource.tf
new file mode 100644
index 0000000000000000000000000000000000000000..1137e181bfc6c43568fd79d7274295a4ab6f181c
--- /dev/null
+++ b/examples/resources/smallstep_managed_configuration/resource.tf
@@ -0,0 +1,27 @@
+
+resource "smallstep_managed_configuration" "mc" {
+  agent_configuration_id = smallstep_agent_configuration.agent1.id
+  host_id                = "9cdaf513-3296-4037-bd9b-d0634f51cd79"
+  name                   = "DB Server"
+  managed_endpoints = [
+    {
+      endpoint_configuration_id = smallstep_endpoint_configuration.x509.id
+      x509_certificate_data = {
+        common_name = "db"
+        sans = [
+          "db",
+          "db.default",
+          "db.default.svc",
+          "db.defaulst.svc.cluster.local",
+        ]
+      }
+    },
+    {
+      endpoint_configuration_id = smallstep_endpoint_configuration.ssh.id
+      ssh_certificate_data = {
+        key_id     = "abc"
+        principals = ["ops", "eng"]
+      }
+    },
+  ]
+}
diff --git a/internal/provider/agent_configuration/data_source.go b/internal/provider/agent_configuration/data_source.go
new file mode 100644
index 0000000000000000000000000000000000000000..fc3883d0a338db8f817cd2341c99eb142f75f33e
--- /dev/null
+++ b/internal/provider/agent_configuration/data_source.go
@@ -0,0 +1,137 @@
+package agent_configuration
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"github.com/hashicorp/terraform-plugin-framework/datasource"
+	"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
+	"github.com/hashicorp/terraform-plugin-log/tflog"
+	v20230301 "github.com/smallstep/terraform-provider-smallstep/internal/apiclient/v20230301"
+	"github.com/smallstep/terraform-provider-smallstep/internal/provider/utils"
+)
+
+var _ datasource.DataSourceWithConfigure = (*DataSource)(nil)
+
+func NewDataSource() datasource.DataSource {
+	return &DataSource{}
+}
+
+// DataSource implements data.smallstep_agent_configuration
+type DataSource struct {
+	client *v20230301.Client
+}
+
+func (a *DataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+	resp.TypeName = typeName
+}
+
+// Configure adds the Smallstep API client to the data source.
+func (a *DataSource) 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.(*v20230301.Client)
+
+	if !ok {
+		resp.Diagnostics.AddError(
+			"Get Smallstep API client from provider",
+			fmt.Sprintf("Expected *v20230301.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+		)
+		return
+	}
+
+	a.client = client
+}
+
+func (a *DataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+	var config Model
+
+	// Read Terraform configuration data into the model
+	resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
+
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	id := config.ID.ValueString()
+
+	httpResp, err := a.client.GetAgentConfiguration(ctx, id, &v20230301.GetAgentConfigurationParams{})
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to read agent configuration %q: %v", id, err),
+		)
+		return
+	}
+	defer httpResp.Body.Close()
+
+	if httpResp.StatusCode != http.StatusOK {
+		resp.Diagnostics.AddError(
+			"Smallstep API Response Error",
+			fmt.Sprintf("Received status %d reading agent configuration %q: %s", httpResp.StatusCode, id, utils.APIErrorMsg(httpResp.Body)),
+		)
+		return
+	}
+
+	ac := &v20230301.AgentConfiguration{}
+	if err := json.NewDecoder(httpResp.Body).Decode(ac); err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to unmarshal agent configuration %q: %v", id, err),
+		)
+		return
+	}
+
+	remote, d := fromAPI(ctx, ac, req.Config)
+	resp.Diagnostics.Append(d...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	tflog.Trace(ctx, fmt.Sprintf("read agent configuration %q data source", id))
+
+	resp.Diagnostics.Append(resp.State.Set(ctx, &remote)...)
+}
+
+func (d *DataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+	component, props, err := utils.Describe("agentConfiguration")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	resp.Schema = schema.Schema{
+		MarkdownDescription: component,
+
+		Attributes: map[string]schema.Attribute{
+			"id": schema.StringAttribute{
+				MarkdownDescription: props["id"],
+				Required:            true,
+			},
+			"attestation_slug": schema.StringAttribute{
+				MarkdownDescription: props["attestationSlug"],
+				Computed:            true,
+			},
+			"authority_id": schema.StringAttribute{
+				MarkdownDescription: props["authorityID"],
+				Computed:            true,
+			},
+			"provisioner_name": schema.StringAttribute{
+				MarkdownDescription: props["provisioner"],
+				Computed:            true,
+			},
+			"name": schema.StringAttribute{
+				MarkdownDescription: props["name"],
+				Computed:            true,
+			},
+		},
+	}
+}
diff --git a/internal/provider/agent_configuration/model.go b/internal/provider/agent_configuration/model.go
new file mode 100644
index 0000000000000000000000000000000000000000..2f85f662f0948e8adabf8acf237194d07fecd4f8
--- /dev/null
+++ b/internal/provider/agent_configuration/model.go
@@ -0,0 +1,47 @@
+package agent_configuration
+
+import (
+	"context"
+
+	"github.com/hashicorp/terraform-plugin-framework/diag"
+	"github.com/hashicorp/terraform-plugin-framework/path"
+	"github.com/hashicorp/terraform-plugin-framework/types"
+	v20230301 "github.com/smallstep/terraform-provider-smallstep/internal/apiclient/v20230301"
+	"github.com/smallstep/terraform-provider-smallstep/internal/provider/utils"
+)
+
+const typeName = "smallstep_agent_configuration"
+
+type Model struct {
+	ID              types.String `tfsdk:"id"`
+	Name            types.String `tfsdk:"name"`
+	Provisioner     types.String `tfsdk:"provisioner_name"`
+	AuthorityID     types.String `tfsdk:"authority_id"`
+	AttestationSlug types.String `tfsdk:"attestation_slug"`
+}
+
+func fromAPI(ctx context.Context, ac *v20230301.AgentConfiguration, state utils.AttributeGetter) (*Model, diag.Diagnostics) {
+	var diags diag.Diagnostics
+
+	model := &Model{
+		ID:          types.StringValue(utils.Deref(ac.Id)),
+		Name:        types.StringValue(ac.Name),
+		Provisioner: types.StringValue(ac.Provisioner),
+		AuthorityID: types.StringValue(ac.AuthorityID),
+	}
+
+	attestationSlug, d := utils.ToOptionalString(ctx, ac.AttestationSlug, state, path.Root("attestation_slug"))
+	diags = append(diags, d...)
+	model.AttestationSlug = attestationSlug
+
+	return model, diags
+}
+
+func toAPI(model *Model) *v20230301.AgentConfiguration {
+	return &v20230301.AgentConfiguration{
+		Name:            model.Name.ValueString(),
+		AuthorityID:     model.AuthorityID.ValueString(),
+		Provisioner:     model.Provisioner.ValueString(),
+		AttestationSlug: model.AttestationSlug.ValueStringPointer(),
+	}
+}
diff --git a/internal/provider/agent_configuration/resource.go b/internal/provider/agent_configuration/resource.go
new file mode 100644
index 0000000000000000000000000000000000000000..6dfd4ff0ac3bcd9f7502845f58c7b48cb784b5ee
--- /dev/null
+++ b/internal/provider/agent_configuration/resource.go
@@ -0,0 +1,246 @@
+package agent_configuration
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"github.com/hashicorp/terraform-plugin-framework/path"
+	"github.com/hashicorp/terraform-plugin-framework/resource"
+	"github.com/hashicorp/terraform-plugin-framework/resource/schema"
+	"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+	"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+	"github.com/hashicorp/terraform-plugin-log/tflog"
+	v20230301 "github.com/smallstep/terraform-provider-smallstep/internal/apiclient/v20230301"
+	"github.com/smallstep/terraform-provider-smallstep/internal/provider/utils"
+)
+
+var _ resource.ResourceWithImportState = (*Resource)(nil)
+
+func NewResource() resource.Resource {
+	return &Resource{}
+}
+
+// Resource defines the resource implementation.
+type Resource struct {
+	client *v20230301.Client
+}
+
+func (r *Resource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+	resp.TypeName = typeName
+}
+
+// Configure adds the Smallstep API client to the resource.
+func (r *Resource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+	// Prevent panic if the provider has not been configured.
+	if req.ProviderData == nil {
+		return
+	}
+
+	client, ok := req.ProviderData.(*v20230301.Client)
+
+	if !ok {
+		resp.Diagnostics.AddError(
+			"Get Smallstep API client from provider",
+			fmt.Sprintf("Expected *v20230301.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+		)
+		return
+	}
+
+	r.client = client
+}
+
+func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+	state := &Model{}
+
+	resp.Diagnostics.Append(req.State.Get(ctx, state)...)
+
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	id := state.ID.ValueString()
+
+	httpResp, err := r.client.GetAgentConfiguration(ctx, id, &v20230301.GetAgentConfigurationParams{})
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to read agent configuration %q: %v", id, err),
+		)
+		return
+	}
+	defer httpResp.Body.Close()
+
+	if httpResp.StatusCode == http.StatusNotFound {
+		resp.State.RemoveResource(ctx)
+		return
+	}
+
+	if httpResp.StatusCode != http.StatusOK {
+		resp.Diagnostics.AddError(
+			"Smallstep API Response Error",
+			fmt.Sprintf("Received status %d reading agent configuration %q: %s", httpResp.StatusCode, id, utils.APIErrorMsg(httpResp.Body)),
+		)
+		return
+	}
+
+	ac := &v20230301.AgentConfiguration{}
+	if err := json.NewDecoder(httpResp.Body).Decode(ac); err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to unmarshal agent configuration %q: %v", id, err),
+		)
+		return
+	}
+
+	remote, d := fromAPI(ctx, ac, req.State)
+	resp.Diagnostics.Append(d...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	tflog.Trace(ctx, fmt.Sprintf("read agent configuration %q resource", id))
+
+	resp.Diagnostics.Append(resp.State.Set(ctx, &remote)...)
+}
+
+func (r *Resource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+	component, props, err := utils.Describe("agentConfiguration")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	resp.Schema = schema.Schema{
+		MarkdownDescription: component,
+
+		Attributes: map[string]schema.Attribute{
+			"id": schema.StringAttribute{
+				MarkdownDescription: props["id"],
+				Computed:            true,
+				PlanModifiers: []planmodifier.String{
+					stringplanmodifier.UseStateForUnknown(),
+				},
+			},
+			"name": schema.StringAttribute{
+				MarkdownDescription: props["name"],
+				Required:            true,
+				PlanModifiers: []planmodifier.String{
+					stringplanmodifier.RequiresReplace(),
+				},
+			},
+			"authority_id": schema.StringAttribute{
+				MarkdownDescription: props["authorityID"],
+				Required:            true,
+				PlanModifiers: []planmodifier.String{
+					stringplanmodifier.RequiresReplace(),
+				},
+			},
+			"provisioner_name": schema.StringAttribute{
+				MarkdownDescription: props["provisioner"],
+				Required:            true,
+				PlanModifiers: []planmodifier.String{
+					stringplanmodifier.RequiresReplace(),
+				},
+			},
+			"attestation_slug": schema.StringAttribute{
+				MarkdownDescription: props["attestationSlug"],
+				Optional:            true,
+				PlanModifiers: []planmodifier.String{
+					stringplanmodifier.RequiresReplace(),
+				},
+			},
+		},
+	}
+}
+
+func (a *Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+	var plan Model
+
+	resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
+
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	reqBody := toAPI(&plan)
+
+	httpResp, err := a.client.PostAgentConfigurations(ctx, &v20230301.PostAgentConfigurationsParams{}, *reqBody)
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to create agent configuration %q: %v", plan.Name.ValueString(), err),
+		)
+		return
+	}
+	defer httpResp.Body.Close()
+
+	if httpResp.StatusCode != http.StatusCreated {
+		resp.Diagnostics.AddError(
+			"Smallstep API Response Error",
+			fmt.Sprintf("Received status %d creating agent configuration %q: %s", httpResp.StatusCode, plan.Name.ValueString(), utils.APIErrorMsg(httpResp.Body)),
+		)
+		return
+	}
+
+	ac := &v20230301.AgentConfiguration{}
+	if err := json.NewDecoder(httpResp.Body).Decode(ac); err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to unmarshal agent configuration %q: %v", plan.Name.ValueString(), err),
+		)
+		return
+	}
+
+	state, diags := fromAPI(ctx, ac, req.Plan)
+	resp.Diagnostics.Append(diags...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	tflog.Trace(ctx, fmt.Sprintf("create agent configuration %q resource", plan.Name.ValueString()))
+
+	resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
+}
+
+func (r *Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+	// Update not supported. All changes require replacement.
+}
+
+func (a *Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+	var state Model
+
+	resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	id := state.ID.ValueString()
+
+	httpResp, err := a.client.DeleteAgentConfiguration(ctx, id, &v20230301.DeleteAgentConfigurationParams{})
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to delete agent configuration %s: %v", id, err),
+		)
+		return
+	}
+	defer httpResp.Body.Close()
+
+	if httpResp.StatusCode != http.StatusNoContent {
+		resp.Diagnostics.AddError(
+			"Smallstep API Response Error",
+			fmt.Sprintf("Received status %d deleting agent configuration %s: %s", httpResp.StatusCode, id, utils.APIErrorMsg(httpResp.Body)),
+		)
+		return
+	}
+}
+
+func (r *Resource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+	resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
diff --git a/internal/provider/endpoint_configuration/data_source.go b/internal/provider/endpoint_configuration/data_source.go
new file mode 100644
index 0000000000000000000000000000000000000000..fe5d1065fc27d801c3ec8140b56ba75c836734cb
--- /dev/null
+++ b/internal/provider/endpoint_configuration/data_source.go
@@ -0,0 +1,313 @@
+package endpoint_configuration
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"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"
+	v20230301 "github.com/smallstep/terraform-provider-smallstep/internal/apiclient/v20230301"
+	"github.com/smallstep/terraform-provider-smallstep/internal/provider/utils"
+)
+
+var _ datasource.DataSourceWithConfigure = (*DataSource)(nil)
+
+func NewDataSource() datasource.DataSource {
+	return &DataSource{}
+}
+
+// DataSource implements data.smallstep_endpoint_configuration
+type DataSource struct {
+	client *v20230301.Client
+}
+
+func (a *DataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+	resp.TypeName = typeName
+}
+
+// Configure adds the Smallstep API client to the data source.
+func (a *DataSource) 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.(*v20230301.Client)
+
+	if !ok {
+		resp.Diagnostics.AddError(
+			"Get Smallstep API client from provider",
+			fmt.Sprintf("Expected *v20230301.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+		)
+		return
+	}
+
+	a.client = client
+}
+
+func (a *DataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+	var config Model
+
+	// Read Terraform configuration data into the model
+	resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
+
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	id := config.ID.ValueString()
+
+	httpResp, err := a.client.GetEndpointConfiguration(ctx, id, &v20230301.GetEndpointConfigurationParams{})
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to read endpoint configuration %q: %v", id, err),
+		)
+		return
+	}
+	defer httpResp.Body.Close()
+
+	if httpResp.StatusCode != http.StatusOK {
+		resp.Diagnostics.AddError(
+			"Smallstep API Response Error",
+			fmt.Sprintf("Received status %d reading endpoint configuration %q: %s", httpResp.StatusCode, id, utils.APIErrorMsg(httpResp.Body)),
+		)
+		return
+	}
+
+	ac := &v20230301.EndpointConfiguration{}
+	if err := json.NewDecoder(httpResp.Body).Decode(ac); err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to unmarshal endpoint configuration %q: %v", id, err),
+		)
+		return
+	}
+
+	remote, d := fromAPI(ctx, ac, req.Config)
+	resp.Diagnostics.Append(d...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	tflog.Trace(ctx, fmt.Sprintf("read endpoint configuration %q data source", id))
+
+	resp.Diagnostics.Append(resp.State.Set(ctx, &remote)...)
+}
+
+func (d *DataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+	component, props, err := utils.Describe("endpointConfiguration")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	certInfo, certInfoProps, err := utils.Describe("endpointCertificateInfo")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	_, hookProps, err := utils.Describe("endpointHook")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	hooks, hooksProps, err := utils.Describe("endpointHooks")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	keyInfo, keyInfoProps, err := utils.Describe("endpointKeyInfo")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	reloadInfo, reloadInfoProps, err := utils.Describe("endpointReloadInfo")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	resp.Schema = schema.Schema{
+		MarkdownDescription: component,
+
+		Attributes: map[string]schema.Attribute{
+			"id": schema.StringAttribute{
+				MarkdownDescription: props["id"],
+				Required:            true,
+			},
+			"kind": schema.StringAttribute{
+				MarkdownDescription: props["kind"],
+				Computed:            true,
+			},
+			"name": schema.StringAttribute{
+				MarkdownDescription: props["name"],
+				Computed:            true,
+			},
+			"authority_id": schema.StringAttribute{
+				MarkdownDescription: props["authorityID"],
+				Computed:            true,
+			},
+			"provisioner_name": schema.StringAttribute{
+				MarkdownDescription: props["provisioner"],
+				Computed:            true,
+			},
+			"key_info": schema.SingleNestedAttribute{
+				Computed:            true,
+				MarkdownDescription: keyInfo,
+				Attributes: map[string]schema.Attribute{
+					"format": schema.StringAttribute{
+						Computed:            true,
+						MarkdownDescription: keyInfoProps["format"],
+					},
+					"pub_file": schema.StringAttribute{
+						Computed:            true,
+						MarkdownDescription: keyInfoProps["pubFile"],
+					},
+					"type": schema.StringAttribute{
+						Computed:            true,
+						MarkdownDescription: keyInfoProps["type"],
+					},
+				},
+			},
+			"reload_info": schema.SingleNestedAttribute{
+				Computed:            true,
+				MarkdownDescription: reloadInfo,
+				Attributes: map[string]schema.Attribute{
+					"method": schema.StringAttribute{
+						Computed:            true,
+						MarkdownDescription: reloadInfoProps["method"],
+					},
+					"pid_file": schema.StringAttribute{
+						Computed:            true,
+						MarkdownDescription: reloadInfoProps["pidFile"],
+					},
+					"signal": schema.Int64Attribute{
+						Computed:            true,
+						MarkdownDescription: reloadInfoProps["signal"],
+					},
+				},
+			},
+			"hooks": schema.SingleNestedAttribute{
+				Computed:            true,
+				MarkdownDescription: hooks,
+				Attributes: map[string]schema.Attribute{
+					"sign": schema.SingleNestedAttribute{
+						Computed:            true,
+						MarkdownDescription: hooksProps["sign"],
+						Attributes: map[string]schema.Attribute{
+							"shell": schema.StringAttribute{
+								Computed:            true,
+								MarkdownDescription: hookProps["shell"],
+							},
+							"before": schema.ListAttribute{
+								ElementType:         types.StringType,
+								Computed:            true,
+								MarkdownDescription: hookProps["before"],
+							},
+							"after": schema.ListAttribute{
+								ElementType:         types.StringType,
+								Computed:            true,
+								MarkdownDescription: hookProps["after"],
+							},
+							"on_error": schema.ListAttribute{
+								ElementType:         types.StringType,
+								Computed:            true,
+								MarkdownDescription: hookProps["onError"],
+							},
+						},
+					},
+					"renew": schema.SingleNestedAttribute{
+						Computed:            true,
+						MarkdownDescription: hooksProps["renew"],
+						Attributes: map[string]schema.Attribute{
+							"shell": schema.StringAttribute{
+								Computed:            true,
+								MarkdownDescription: hookProps["shell"],
+							},
+							"before": schema.ListAttribute{
+								ElementType:         types.StringType,
+								Computed:            true,
+								MarkdownDescription: hookProps["before"],
+							},
+							"after": schema.ListAttribute{
+								ElementType:         types.StringType,
+								Computed:            true,
+								MarkdownDescription: hookProps["after"],
+							},
+							"on_error": schema.ListAttribute{
+								ElementType:         types.StringType,
+								Computed:            true,
+								MarkdownDescription: hookProps["onError"],
+							},
+						},
+					},
+				},
+			},
+			"certificate_info": schema.SingleNestedAttribute{
+				Computed:            true,
+				MarkdownDescription: certInfo,
+				Attributes: map[string]schema.Attribute{
+					"type": schema.StringAttribute{
+						MarkdownDescription: certInfoProps["type"],
+						Computed:            true,
+					},
+					"duration": schema.StringAttribute{
+						MarkdownDescription: certInfoProps["duration"],
+						Computed:            true,
+					},
+					"crt_file": schema.StringAttribute{
+						MarkdownDescription: certInfoProps["crtFile"],
+						Computed:            true,
+					},
+					"key_file": schema.StringAttribute{
+						MarkdownDescription: certInfoProps["keyFile"],
+						Computed:            true,
+					},
+					"root_file": schema.StringAttribute{
+						MarkdownDescription: certInfoProps["rootFile"],
+						Computed:            true,
+					},
+					"uid": schema.Int64Attribute{
+						MarkdownDescription: certInfoProps["uid"],
+						Computed:            true,
+					},
+					"gid": schema.Int64Attribute{
+						MarkdownDescription: certInfoProps["gid"],
+						Computed:            true,
+					},
+					"mode": schema.Int64Attribute{
+						MarkdownDescription: certInfoProps["mode"],
+						Computed:            true,
+					},
+				},
+			},
+		},
+	}
+}
diff --git a/internal/provider/endpoint_configuration/model.go b/internal/provider/endpoint_configuration/model.go
new file mode 100644
index 0000000000000000000000000000000000000000..7df0a6fc2648239232fd02625dfdd151075d3cc2
--- /dev/null
+++ b/internal/provider/endpoint_configuration/model.go
@@ -0,0 +1,341 @@
+package endpoint_configuration
+
+import (
+	"context"
+
+	"github.com/hashicorp/terraform-plugin-framework/attr"
+	"github.com/hashicorp/terraform-plugin-framework/diag"
+	"github.com/hashicorp/terraform-plugin-framework/path"
+	"github.com/hashicorp/terraform-plugin-framework/types"
+	"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
+	v20230301 "github.com/smallstep/terraform-provider-smallstep/internal/apiclient/v20230301"
+	"github.com/smallstep/terraform-provider-smallstep/internal/provider/utils"
+)
+
+const typeName = "smallstep_endpoint_configuration"
+
+type Model struct {
+	ID              types.String          `tfsdk:"id"`
+	Name            types.String          `tfsdk:"name"`
+	AuthorityID     types.String          `tfsdk:"authority_id"`
+	Provisioner     types.String          `tfsdk:"provisioner_name"`
+	Kind            types.String          `tfsdk:"kind"`
+	CertificateInfo *CertificateInfoModel `tfsdk:"certificate_info"`
+	KeyInfo         *KeyInfoModel         `tfsdk:"key_info"`
+	// ReloadInfo and Hooks are optional. Need to use Object type to support
+	// the "unknown" state.
+	ReloadInfo types.Object `tfsdk:"reload_info"`
+	Hooks      types.Object `tfsdk:"hooks"`
+}
+
+type CertificateInfoModel struct {
+	Type     types.String `tfsdk:"type"`
+	CrtFile  types.String `tfsdk:"crt_file"`
+	KeyFile  types.String `tfsdk:"key_file"`
+	RootFile types.String `tfsdk:"root_file"`
+	Duration types.String `tfsdk:"duration"`
+	GID      types.Int64  `tfsdk:"gid"`
+	UID      types.Int64  `tfsdk:"uid"`
+	Mode     types.Int64  `tfsdk:"mode"`
+}
+
+func (ci CertificateInfoModel) toAPI() v20230301.EndpointCertificateInfo {
+	return v20230301.EndpointCertificateInfo{
+		Type:     v20230301.EndpointCertificateInfoType(ci.Type.ValueString()),
+		Duration: ci.Duration.ValueStringPointer(),
+		CrtFile:  ci.CrtFile.ValueStringPointer(),
+		KeyFile:  ci.KeyFile.ValueStringPointer(),
+		RootFile: ci.RootFile.ValueStringPointer(),
+		Uid:      utils.ToIntPointer(ci.UID.ValueInt64Pointer()),
+		Gid:      utils.ToIntPointer(ci.GID.ValueInt64Pointer()),
+		Mode:     utils.ToIntPointer(ci.Mode.ValueInt64Pointer()),
+	}
+}
+
+type HookModel struct {
+	Shell   types.String `tfsdk:"shell"`
+	Before  types.List   `tfsdk:"before"`
+	After   types.List   `tfsdk:"after"`
+	OnError types.List   `tfsdk:"on_error"`
+}
+
+var hookObjectType = types.ObjectType{
+	AttrTypes: map[string]attr.Type{
+		"shell": types.StringType,
+		"before": types.ListType{
+			ElemType: types.StringType,
+		},
+		"after": types.ListType{
+			ElemType: types.StringType,
+		},
+		"on_error": types.ListType{
+			ElemType: types.StringType,
+		},
+	},
+}
+
+func (h *HookModel) toAPI(ctx context.Context) (*v20230301.EndpointHook, diag.Diagnostics) {
+	var diags diag.Diagnostics
+
+	if h == nil {
+		return nil, diags
+	}
+
+	var before *[]string
+	var after *[]string
+	var onError *[]string
+
+	if !h.Before.IsNull() {
+		diags.Append(h.Before.ElementsAs(ctx, &before, false)...)
+	}
+	if !h.After.IsNull() {
+		diags.Append(h.After.ElementsAs(ctx, &after, false)...)
+	}
+	if !h.OnError.IsNull() {
+		diags.Append(h.OnError.ElementsAs(ctx, &onError, false)...)
+	}
+
+	return &v20230301.EndpointHook{
+		Shell:   h.Shell.ValueStringPointer(),
+		Before:  before,
+		After:   after,
+		OnError: onError,
+	}, diags
+}
+
+type HooksModel struct {
+	Sign  types.Object `tfsdk:"sign"`
+	Renew types.Object `tfsdk:"renew"`
+}
+
+var hooksObjectType = map[string]attr.Type{
+	"sign":  hookObjectType,
+	"renew": hookObjectType,
+}
+
+func hookToAPI(ctx context.Context, hook types.Object) (*v20230301.EndpointHook, diag.Diagnostics) {
+	hookModel := &HookModel{}
+	diags := hook.As(ctx, &hookModel, basetypes.ObjectAsOptions{
+		UnhandledUnknownAsEmpty: true,
+	})
+
+	h, d := hookModel.toAPI(ctx)
+	diags.Append(d...)
+
+	return h, diags
+}
+
+func (h *HooksModel) toAPI(ctx context.Context) (*v20230301.EndpointHooks, diag.Diagnostics) {
+	var diags diag.Diagnostics
+
+	if h == nil {
+		return nil, diags
+	}
+
+	sign, d := hookToAPI(ctx, h.Sign)
+	diags.Append(d...)
+
+	renew, d := hookToAPI(ctx, h.Renew)
+	diags.Append(d...)
+
+	return &v20230301.EndpointHooks{
+		Sign:  sign,
+		Renew: renew,
+	}, diags
+}
+
+type KeyInfoModel struct {
+	Format  types.String `tfsdk:"format"`
+	PubFile types.String `tfsdk:"pub_file"`
+	Type    types.String `tfsdk:"type"`
+}
+
+func (ki *KeyInfoModel) toAPI() *v20230301.EndpointKeyInfo {
+	if ki == nil {
+		return nil
+	}
+
+	return &v20230301.EndpointKeyInfo{
+		Format:  utils.ToStringPointer[string, v20230301.EndpointKeyInfoFormat](ki.Format.ValueStringPointer()),
+		PubFile: ki.PubFile.ValueStringPointer(),
+		Type:    utils.ToStringPointer[string, v20230301.EndpointKeyInfoType](ki.Type.ValueStringPointer()),
+	}
+}
+
+type ReloadInfoModel struct {
+	Method  types.String `tfsdk:"method"`
+	PIDFile types.String `tfsdk:"pid_file"`
+	Signal  types.Int64  `tfsdk:"signal"`
+}
+
+var reloadInfoType = map[string]attr.Type{
+	"method":   types.StringType,
+	"pid_file": types.StringType,
+	"signal":   types.Int64Type,
+}
+
+func (ri *ReloadInfoModel) toAPI() *v20230301.EndpointReloadInfo {
+	if ri == nil {
+		return nil
+	}
+
+	return &v20230301.EndpointReloadInfo{
+		Method:  v20230301.EndpointReloadInfoMethod(ri.Method.ValueString()),
+		PidFile: ri.PIDFile.ValueStringPointer(),
+		Signal:  utils.ToIntPointer(ri.Signal.ValueInt64Pointer()),
+	}
+}
+
+func fromAPI(ctx context.Context, ec *v20230301.EndpointConfiguration, state utils.AttributeGetter) (*Model, diag.Diagnostics) {
+	var diags diag.Diagnostics
+
+	ciDuration, d := utils.ToEqualString(ctx, ec.CertificateInfo.Duration, state, path.Root("certificate_info").AtName("duration"), utils.IsDurationEqual)
+	diags = append(diags, d...)
+
+	ciCrtFile, d := utils.ToOptionalString(ctx, ec.CertificateInfo.CrtFile, state, path.Root("certificate_info").AtName("crt_file"))
+	diags = append(diags, d...)
+
+	ciKeyFile, d := utils.ToOptionalString(ctx, ec.CertificateInfo.KeyFile, state, path.Root("certificate_info").AtName("key_file"))
+	diags = append(diags, d...)
+
+	ciRootFile, d := utils.ToOptionalString(ctx, ec.CertificateInfo.RootFile, state, path.Root("certificate_info").AtName("root_file"))
+	diags = append(diags, d...)
+
+	ciUID, d := utils.ToOptionalInt(ctx, ec.CertificateInfo.Uid, state, path.Root("certificate_info").AtName("uid"))
+	diags = append(diags, d...)
+
+	ciGID, d := utils.ToOptionalInt(ctx, ec.CertificateInfo.Gid, state, path.Root("certificate_info").AtName("gid"))
+	diags = append(diags, d...)
+
+	ciMode, d := utils.ToOptionalInt(ctx, ec.CertificateInfo.Mode, state, path.Root("certificate_info").AtName("mode"))
+	diags = append(diags, d...)
+
+	model := &Model{
+		ID:          types.StringValue(utils.Deref(ec.Id)),
+		Name:        types.StringValue(ec.Name),
+		Kind:        types.StringValue(string(ec.Kind)),
+		AuthorityID: types.StringValue(ec.AuthorityID),
+		Provisioner: types.StringValue(ec.Provisioner),
+		CertificateInfo: &CertificateInfoModel{
+			Type:     types.StringValue(string(ec.CertificateInfo.Type)),
+			Duration: ciDuration,
+			CrtFile:  ciCrtFile,
+			KeyFile:  ciKeyFile,
+			RootFile: ciRootFile,
+			UID:      ciUID,
+			GID:      ciGID,
+			Mode:     ciMode,
+		},
+	}
+
+	if ec.Hooks != nil {
+		sign, d := hookFromAPI(ctx, ec.Hooks.Sign, path.Root("hooks").AtName("sign"), state)
+		diags.Append(d...)
+
+		renew, d := hookFromAPI(ctx, ec.Hooks.Renew, path.Root("hooks").AtName("renew"), state)
+		diags.Append(d...)
+
+		hooksObj, d := basetypes.NewObjectValue(hooksObjectType, map[string]attr.Value{
+			"sign":  sign,
+			"renew": renew,
+		})
+		diags.Append(d...)
+
+		model.Hooks = hooksObj
+	} else {
+		model.Hooks = basetypes.NewObjectNull(hooksObjectType)
+	}
+
+	if ec.KeyInfo != nil {
+		format, d := utils.ToOptionalString(ctx, ec.KeyInfo.Format, state, path.Root("key_info").AtName("format"))
+		diags = append(diags, d...)
+
+		pubFile, d := utils.ToOptionalString(ctx, ec.KeyInfo.PubFile, state, path.Root("key_info").AtName("pub_file"))
+		diags = append(diags, d...)
+
+		typ, d := utils.ToOptionalString(ctx, ec.KeyInfo.Type, state, path.Root("key_info").AtName("type"))
+		diags = append(diags, d...)
+
+		model.KeyInfo = &KeyInfoModel{
+			Format:  format,
+			PubFile: pubFile,
+			Type:    typ,
+		}
+	}
+
+	if ec.ReloadInfo != nil {
+		pidFile, d := utils.ToOptionalString(ctx, ec.ReloadInfo.PidFile, state, path.Root("reload_info").AtName("method"))
+		diags.Append(d...)
+
+		signal, d := utils.ToOptionalInt(ctx, ec.ReloadInfo.Signal, state, path.Root("reload_info").AtName("signal"))
+		diags.Append(d...)
+
+		reloadInfoObject, d := basetypes.NewObjectValue(reloadInfoType, map[string]attr.Value{
+			"method":   types.StringValue(string(ec.ReloadInfo.Method)),
+			"pid_file": pidFile,
+			"signal":   signal,
+		})
+		diags.Append(d...)
+		model.ReloadInfo = reloadInfoObject
+	} else {
+		model.ReloadInfo = basetypes.NewObjectNull(reloadInfoType)
+	}
+
+	return model, diags
+}
+
+func toAPI(ctx context.Context, model *Model) (*v20230301.EndpointConfiguration, diag.Diagnostics) {
+	hooksModel := &HooksModel{}
+	diags := model.Hooks.As(ctx, &hooksModel, basetypes.ObjectAsOptions{
+		UnhandledUnknownAsEmpty: true,
+	})
+	hooks, d := hooksModel.toAPI(ctx)
+	diags.Append(d...)
+
+	reloadInfo := &ReloadInfoModel{}
+	d = model.ReloadInfo.As(ctx, &reloadInfo, basetypes.ObjectAsOptions{
+		UnhandledUnknownAsEmpty: true,
+	})
+	diags.Append(d...)
+
+	return &v20230301.EndpointConfiguration{
+		Name:            model.Name.ValueString(),
+		Kind:            v20230301.EndpointConfigurationKind(model.Kind.ValueString()),
+		AuthorityID:     model.AuthorityID.ValueString(),
+		Provisioner:     model.Provisioner.ValueString(),
+		CertificateInfo: model.CertificateInfo.toAPI(),
+		KeyInfo:         model.KeyInfo.toAPI(),
+		Hooks:           hooks,
+		ReloadInfo:      reloadInfo.toAPI(),
+	}, diags
+}
+
+func hookFromAPI(ctx context.Context, hook *v20230301.EndpointHook, hookPath path.Path, state utils.AttributeGetter) (types.Object, diag.Diagnostics) {
+	var diags diag.Diagnostics
+
+	if hook == nil {
+		return basetypes.NewObjectNull(hookObjectType.AttrTypes), diags
+	}
+
+	shell, d := utils.ToOptionalString(ctx, hook.Shell, state, hookPath.AtName("shell"))
+	diags = append(diags, d...)
+
+	before, d := utils.ToOptionalList(ctx, hook.Before, state, hookPath.AtName("before"))
+	diags = append(diags, d...)
+
+	after, d := utils.ToOptionalList(ctx, hook.After, state, hookPath.AtName("after"))
+	diags = append(diags, d...)
+
+	onError, d := utils.ToOptionalList(ctx, hook.OnError, state, hookPath.AtName("on_error"))
+	diags = append(diags, d...)
+
+	obj, d := basetypes.NewObjectValue(hookObjectType.AttrTypes, map[string]attr.Value{
+		"shell":    shell,
+		"before":   before,
+		"after":    after,
+		"on_error": onError,
+	})
+	diags.Append(d...)
+
+	return obj, diags
+}
diff --git a/internal/provider/endpoint_configuration/resource.go b/internal/provider/endpoint_configuration/resource.go
new file mode 100644
index 0000000000000000000000000000000000000000..e72a36238aee80898e6a619081104a603756a26b
--- /dev/null
+++ b/internal/provider/endpoint_configuration/resource.go
@@ -0,0 +1,472 @@
+package endpoint_configuration
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"github.com/hashicorp/terraform-plugin-framework/path"
+	"github.com/hashicorp/terraform-plugin-framework/resource"
+	"github.com/hashicorp/terraform-plugin-framework/resource/schema"
+	"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
+	"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+	"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+	"github.com/hashicorp/terraform-plugin-framework/types"
+	"github.com/hashicorp/terraform-plugin-log/tflog"
+	v20230301 "github.com/smallstep/terraform-provider-smallstep/internal/apiclient/v20230301"
+	"github.com/smallstep/terraform-provider-smallstep/internal/provider/utils"
+)
+
+var _ resource.ResourceWithImportState = (*Resource)(nil)
+
+func NewResource() resource.Resource {
+	return &Resource{}
+}
+
+// Resource defines the resource implementation.
+type Resource struct {
+	client *v20230301.Client
+}
+
+func (r *Resource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+	resp.TypeName = typeName
+}
+
+// Configure adds the Smallstep API client to the resource.
+func (r *Resource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+	// Prevent panic if the provider has not been configured.
+	if req.ProviderData == nil {
+		return
+	}
+
+	client, ok := req.ProviderData.(*v20230301.Client)
+
+	if !ok {
+		resp.Diagnostics.AddError(
+			"Get Smallstep API client from provider",
+			fmt.Sprintf("Expected *v20230301.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+		)
+		return
+	}
+
+	r.client = client
+}
+
+func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+	state := &Model{}
+	resp.Diagnostics.Append(req.State.Get(ctx, state)...)
+
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	id := state.ID.ValueString()
+
+	httpResp, err := r.client.GetEndpointConfiguration(ctx, id, &v20230301.GetEndpointConfigurationParams{})
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to read endpoint configuration %q: %v", id, err),
+		)
+		return
+	}
+	defer httpResp.Body.Close()
+
+	if httpResp.StatusCode == http.StatusNotFound {
+		resp.State.RemoveResource(ctx)
+		return
+	}
+
+	if httpResp.StatusCode != http.StatusOK {
+		resp.Diagnostics.AddError(
+			"Smallstep API Response Error",
+			fmt.Sprintf("Received status %d reading endpoint configuration %q: %s", httpResp.StatusCode, id, utils.APIErrorMsg(httpResp.Body)),
+		)
+		return
+	}
+
+	ac := &v20230301.EndpointConfiguration{}
+	if err := json.NewDecoder(httpResp.Body).Decode(ac); err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to unmarshal endpoint configuration %q: %v", id, err),
+		)
+		return
+	}
+
+	remote, d := fromAPI(ctx, ac, req.State)
+	resp.Diagnostics.Append(d...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	tflog.Trace(ctx, fmt.Sprintf("read endpoint configuration %q resource", id))
+
+	resp.Diagnostics.Append(resp.State.Set(ctx, &remote)...)
+}
+
+func (r *Resource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+	component, props, err := utils.Describe("endpointConfiguration")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	certInfo, certInfoProps, err := utils.Describe("endpointCertificateInfo")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	_, hookProps, err := utils.Describe("endpointHook")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	hooks, hooksProps, err := utils.Describe("endpointHooks")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	keyInfo, keyInfoProps, err := utils.Describe("endpointKeyInfo")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	reloadInfo, reloadInfoProps, err := utils.Describe("endpointReloadInfo")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	resp.Schema = schema.Schema{
+		MarkdownDescription: component,
+
+		Attributes: map[string]schema.Attribute{
+			"id": schema.StringAttribute{
+				MarkdownDescription: props["id"],
+				Computed:            true,
+				PlanModifiers: []planmodifier.String{
+					stringplanmodifier.UseStateForUnknown(),
+				},
+			},
+			"name": schema.StringAttribute{
+				MarkdownDescription: props["name"],
+				Required:            true,
+				PlanModifiers: []planmodifier.String{
+					stringplanmodifier.RequiresReplace(),
+				},
+			},
+			"kind": schema.StringAttribute{
+				MarkdownDescription: props["kind"],
+				Required:            true,
+				PlanModifiers: []planmodifier.String{
+					stringplanmodifier.RequiresReplace(),
+				},
+			},
+			"authority_id": schema.StringAttribute{
+				MarkdownDescription: props["authorityID"],
+				Required:            true,
+				PlanModifiers: []planmodifier.String{
+					stringplanmodifier.RequiresReplace(),
+				},
+			},
+			"provisioner_name": schema.StringAttribute{
+				MarkdownDescription: props["provisioner"],
+				Required:            true,
+				PlanModifiers: []planmodifier.String{
+					stringplanmodifier.RequiresReplace(),
+				},
+			},
+			"key_info": schema.SingleNestedAttribute{
+				// This object is not required by the API but a default object
+				// will always be returned with both format and type set to
+				// "DEFAULT". To avoid "inconsistent result after apply" errors
+				// require these fields to be set explicitly in terraform.
+				Required:            true,
+				MarkdownDescription: keyInfo,
+				Attributes: map[string]schema.Attribute{
+					"type": schema.StringAttribute{
+						Required:            true,
+						MarkdownDescription: keyInfoProps["type"],
+						PlanModifiers: []planmodifier.String{
+							stringplanmodifier.RequiresReplace(),
+						},
+					},
+					"format": schema.StringAttribute{
+						Required:            true,
+						MarkdownDescription: keyInfoProps["format"],
+						PlanModifiers: []planmodifier.String{
+							stringplanmodifier.RequiresReplace(),
+						},
+					},
+					"pub_file": schema.StringAttribute{
+						Optional:            true,
+						MarkdownDescription: keyInfoProps["pubFile"],
+						PlanModifiers: []planmodifier.String{
+							stringplanmodifier.RequiresReplace(),
+						},
+					},
+				},
+			},
+			"reload_info": schema.SingleNestedAttribute{
+				Optional:            true,
+				Computed:            true,
+				MarkdownDescription: reloadInfo,
+				Attributes: map[string]schema.Attribute{
+					"method": schema.StringAttribute{
+						Required:            true,
+						MarkdownDescription: reloadInfoProps["method"],
+						PlanModifiers: []planmodifier.String{
+							stringplanmodifier.RequiresReplace(),
+						},
+					},
+					"pid_file": schema.StringAttribute{
+						Optional:            true,
+						MarkdownDescription: reloadInfoProps["pidFile"],
+						PlanModifiers: []planmodifier.String{
+							stringplanmodifier.RequiresReplace(),
+						},
+					},
+					"signal": schema.Int64Attribute{
+						Optional:            true,
+						MarkdownDescription: reloadInfoProps["signal"],
+						PlanModifiers: []planmodifier.Int64{
+							int64planmodifier.RequiresReplace(),
+						},
+					},
+				},
+			},
+			"hooks": schema.SingleNestedAttribute{
+				Optional:            true,
+				Computed:            true,
+				MarkdownDescription: hooks,
+				Attributes: map[string]schema.Attribute{
+					"sign": schema.SingleNestedAttribute{
+						Optional:            true,
+						MarkdownDescription: hooksProps["sign"],
+						Attributes: map[string]schema.Attribute{
+							"shell": schema.StringAttribute{
+								Optional:            true,
+								MarkdownDescription: hookProps["shell"],
+							},
+							"before": schema.ListAttribute{
+								ElementType:         types.StringType,
+								Optional:            true,
+								MarkdownDescription: hookProps["before"],
+							},
+							"after": schema.ListAttribute{
+								ElementType:         types.StringType,
+								Optional:            true,
+								MarkdownDescription: hookProps["after"],
+							},
+							"on_error": schema.ListAttribute{
+								ElementType:         types.StringType,
+								Optional:            true,
+								MarkdownDescription: hookProps["onError"],
+							},
+						},
+					},
+					"renew": schema.SingleNestedAttribute{
+						Optional:            true,
+						MarkdownDescription: hooksProps["renew"],
+						Attributes: map[string]schema.Attribute{
+							"shell": schema.StringAttribute{
+								Optional:            true,
+								MarkdownDescription: hookProps["shell"],
+							},
+							"before": schema.ListAttribute{
+								ElementType:         types.StringType,
+								Optional:            true,
+								MarkdownDescription: hookProps["before"],
+							},
+							"after": schema.ListAttribute{
+								ElementType:         types.StringType,
+								Optional:            true,
+								MarkdownDescription: hookProps["after"],
+							},
+							"on_error": schema.ListAttribute{
+								ElementType:         types.StringType,
+								Optional:            true,
+								MarkdownDescription: hookProps["onError"],
+							},
+						},
+					},
+				},
+			},
+			"certificate_info": schema.SingleNestedAttribute{
+				Required:            true,
+				MarkdownDescription: certInfo,
+				Attributes: map[string]schema.Attribute{
+					"type": schema.StringAttribute{
+						MarkdownDescription: certInfoProps["type"],
+						Required:            true,
+						PlanModifiers: []planmodifier.String{
+							stringplanmodifier.RequiresReplace(),
+						},
+					},
+					"duration": schema.StringAttribute{
+						MarkdownDescription: certInfoProps["duration"],
+						Optional:            true,
+						PlanModifiers: []planmodifier.String{
+							stringplanmodifier.RequiresReplace(),
+						},
+					},
+					"crt_file": schema.StringAttribute{
+						MarkdownDescription: certInfoProps["crtFile"],
+						Optional:            true,
+						PlanModifiers: []planmodifier.String{
+							stringplanmodifier.RequiresReplace(),
+						},
+					},
+					"key_file": schema.StringAttribute{
+						MarkdownDescription: certInfoProps["keyFile"],
+						Optional:            true,
+						PlanModifiers: []planmodifier.String{
+							stringplanmodifier.RequiresReplace(),
+						},
+					},
+					"root_file": schema.StringAttribute{
+						MarkdownDescription: certInfoProps["rootFile"],
+						Optional:            true,
+						PlanModifiers: []planmodifier.String{
+							stringplanmodifier.RequiresReplace(),
+						},
+					},
+					"uid": schema.Int64Attribute{
+						MarkdownDescription: certInfoProps["uid"],
+						Optional:            true,
+						PlanModifiers: []planmodifier.Int64{
+							int64planmodifier.RequiresReplace(),
+						},
+					},
+					"gid": schema.Int64Attribute{
+						MarkdownDescription: certInfoProps["gid"],
+						Optional:            true,
+						PlanModifiers: []planmodifier.Int64{
+							int64planmodifier.RequiresReplace(),
+						},
+					},
+					"mode": schema.Int64Attribute{
+						MarkdownDescription: certInfoProps["mode"],
+						Optional:            true,
+						PlanModifiers: []planmodifier.Int64{
+							int64planmodifier.RequiresReplace(),
+						},
+					},
+				},
+			},
+		},
+	}
+}
+
+func (a *Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+	plan := &Model{}
+	resp.Diagnostics.Append(req.Plan.Get(ctx, plan)...)
+
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	reqBody, diags := toAPI(ctx, plan)
+	resp.Diagnostics.Append(diags...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	httpResp, err := a.client.PostEndpointConfigurations(ctx, &v20230301.PostEndpointConfigurationsParams{}, *reqBody)
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to create endpoint configuration %q: %v", plan.Name.ValueString(), err),
+		)
+		return
+	}
+	defer httpResp.Body.Close()
+
+	if httpResp.StatusCode != http.StatusCreated {
+		resp.Diagnostics.AddError(
+			"Smallstep API Response Error",
+			fmt.Sprintf("Received status %d creating endpoint configuration %q: %s", httpResp.StatusCode, plan.Name.ValueString(), utils.APIErrorMsg(httpResp.Body)),
+		)
+		return
+	}
+
+	ac := &v20230301.EndpointConfiguration{}
+	if err := json.NewDecoder(httpResp.Body).Decode(ac); err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to unmarshal endpoint configuration %q: %v", plan.Name.ValueString(), err),
+		)
+		return
+	}
+
+	state, diags := fromAPI(ctx, ac, req.Plan)
+	resp.Diagnostics.Append(diags...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	tflog.Trace(ctx, fmt.Sprintf("create endpoint configuration %q resource", plan.Name.ValueString()))
+
+	resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
+}
+
+func (r *Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+	// Update not supported. All changes require replacement.
+}
+
+func (a *Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+	state := &Model{}
+	resp.Diagnostics.Append(req.State.Get(ctx, state)...)
+
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	id := state.ID.ValueString()
+
+	httpResp, err := a.client.DeleteEndpointConfiguration(ctx, id, &v20230301.DeleteEndpointConfigurationParams{})
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to delete endpoint configuration %s: %v", id, err),
+		)
+		return
+	}
+	defer httpResp.Body.Close()
+
+	if httpResp.StatusCode != http.StatusNoContent {
+		resp.Diagnostics.AddError(
+			"Smallstep API Response Error",
+			fmt.Sprintf("Received status %d deleting endpoint configuration %s: %s", httpResp.StatusCode, id, utils.APIErrorMsg(httpResp.Body)),
+		)
+		return
+	}
+}
+
+func (r *Resource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+	resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
diff --git a/internal/provider/managed_configuration/data_source.go b/internal/provider/managed_configuration/data_source.go
new file mode 100644
index 0000000000000000000000000000000000000000..2675c370b4b323335af767593cbc8b675d5975dd
--- /dev/null
+++ b/internal/provider/managed_configuration/data_source.go
@@ -0,0 +1,207 @@
+package managed_configuration
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"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"
+	v20230301 "github.com/smallstep/terraform-provider-smallstep/internal/apiclient/v20230301"
+	"github.com/smallstep/terraform-provider-smallstep/internal/provider/utils"
+)
+
+var _ datasource.DataSourceWithConfigure = (*DataSource)(nil)
+
+func NewDataSource() datasource.DataSource {
+	return &DataSource{}
+}
+
+// DataSource implements data.smallstep_managed_configuration
+type DataSource struct {
+	client *v20230301.Client
+}
+
+func (a *DataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
+	resp.TypeName = typeName
+}
+
+// Configure adds the Smallstep API client to the data source.
+func (a *DataSource) 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.(*v20230301.Client)
+
+	if !ok {
+		resp.Diagnostics.AddError(
+			"Get Smallstep API client from provider",
+			fmt.Sprintf("Expected *v20230301.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+		)
+		return
+	}
+
+	a.client = client
+}
+
+func (a *DataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
+	var config Model
+
+	// Read Terraform configuration data into the model
+	resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
+
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	id := config.ID.ValueString()
+
+	httpResp, err := a.client.GetManagedConfiguration(ctx, id, &v20230301.GetManagedConfigurationParams{})
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to read managed configuration %q: %v", id, err),
+		)
+		return
+	}
+	defer httpResp.Body.Close()
+
+	if httpResp.StatusCode != http.StatusOK {
+		resp.Diagnostics.AddError(
+			"Smallstep API Response Error",
+			fmt.Sprintf("Received status %d reading managed configuration %q: %s", httpResp.StatusCode, id, utils.APIErrorMsg(httpResp.Body)),
+		)
+		return
+	}
+
+	ac := &v20230301.ManagedConfiguration{}
+	if err := json.NewDecoder(httpResp.Body).Decode(ac); err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to unmarshal managed configuration %q: %v", id, err),
+		)
+		return
+	}
+
+	remote, d := fromAPI(ctx, ac, req.Config)
+	resp.Diagnostics.Append(d...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	tflog.Trace(ctx, fmt.Sprintf("read managed configuration %q data source", id))
+
+	resp.Diagnostics.Append(resp.State.Set(ctx, &remote)...)
+}
+
+func (d *DataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
+	component, props, err := utils.Describe("managedConfiguration")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	me, meProps, err := utils.Describe("managedEndpoint")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	x509, x509Props, err := utils.Describe("endpointX509CertificateData")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	ssh, sshProps, err := utils.Describe("endpointSSHCertificateData")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	resp.Schema = schema.Schema{
+		MarkdownDescription: component,
+
+		Attributes: map[string]schema.Attribute{
+			"id": schema.StringAttribute{
+				MarkdownDescription: props["id"],
+				Required:            true,
+			},
+			"name": schema.StringAttribute{
+				MarkdownDescription: props["name"],
+				Computed:            true,
+			},
+			"agent_configuration_id": schema.StringAttribute{
+				MarkdownDescription: props["agentConfigurationID"],
+				Computed:            true,
+			},
+			"host_id": schema.StringAttribute{
+				MarkdownDescription: props["hostID"],
+				Computed:            true,
+			},
+			"managed_endpoints": schema.SetNestedAttribute{
+				MarkdownDescription: me,
+				Computed:            true,
+				NestedObject: schema.NestedAttributeObject{
+					Attributes: map[string]schema.Attribute{
+						"id": schema.StringAttribute{
+							MarkdownDescription: meProps["id"],
+							Computed:            true,
+						},
+						"endpoint_configuration_id": schema.StringAttribute{
+							MarkdownDescription: meProps["endpointConfigurationID"],
+							Computed:            true,
+						},
+						"x509_certificate_data": schema.SingleNestedAttribute{
+							MarkdownDescription: x509,
+							Computed:            true,
+							Attributes: map[string]schema.Attribute{
+								"common_name": schema.StringAttribute{
+									MarkdownDescription: x509Props["commonName"],
+									Computed:            true,
+								},
+								"sans": schema.SetAttribute{
+									ElementType:         types.StringType,
+									MarkdownDescription: x509Props["sans"],
+									Computed:            true,
+								},
+							},
+						},
+						"ssh_certificate_data": schema.SingleNestedAttribute{
+							MarkdownDescription: ssh,
+							Computed:            true,
+							Attributes: map[string]schema.Attribute{
+								"key_id": schema.StringAttribute{
+									MarkdownDescription: sshProps["keyID"],
+									Computed:            true,
+								},
+								"principals": schema.SetAttribute{
+									ElementType:         types.StringType,
+									MarkdownDescription: sshProps["principals"],
+									Computed:            true,
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+}
diff --git a/internal/provider/managed_configuration/model.go b/internal/provider/managed_configuration/model.go
new file mode 100644
index 0000000000000000000000000000000000000000..3ee9ee4b2b0c2c0f0eb8a7bed98849474fa4f64f
--- /dev/null
+++ b/internal/provider/managed_configuration/model.go
@@ -0,0 +1,161 @@
+package managed_configuration
+
+import (
+	"context"
+
+	"github.com/hashicorp/terraform-plugin-framework/attr"
+	"github.com/hashicorp/terraform-plugin-framework/diag"
+	"github.com/hashicorp/terraform-plugin-framework/path"
+	"github.com/hashicorp/terraform-plugin-framework/types"
+	v20230301 "github.com/smallstep/terraform-provider-smallstep/internal/apiclient/v20230301"
+	"github.com/smallstep/terraform-provider-smallstep/internal/provider/utils"
+)
+
+const typeName = "smallstep_managed_configuration"
+
+type Model struct {
+	ID                   types.String      `tfsdk:"id"`
+	Name                 types.String      `tfsdk:"name"`
+	AgentConfigurationID types.String      `tfsdk:"agent_configuration_id"`
+	HostID               types.String      `tfsdk:"host_id"`
+	ManagedEndpoints     []ManagedEndpoint `tfsdk:"managed_endpoints"`
+}
+
+func (model Model) toAPI(ctx context.Context) (v20230301.ManagedConfiguration, diag.Diagnostics) {
+	var diags diag.Diagnostics
+
+	mc := v20230301.ManagedConfiguration{
+		Id:                   model.ID.ValueStringPointer(),
+		Name:                 model.Name.ValueString(),
+		AgentConfigurationID: model.AgentConfigurationID.ValueString(),
+		HostID:               model.HostID.ValueStringPointer(),
+	}
+
+	for _, me := range model.ManagedEndpoints {
+		me, d := me.toAPI(ctx)
+		diags.Append(d...)
+		mc.ManagedEndpoints = append(mc.ManagedEndpoints, me)
+	}
+
+	return mc, diags
+}
+
+type ManagedEndpoint struct {
+	ID                      types.String         `tfsdk:"id"`
+	EndpointConfigurationID types.String         `tfsdk:"endpoint_configuration_id"`
+	SSHCertificateData      *SSHCertificateData  `tfsdk:"ssh_certificate_data"`
+	X509CertificateData     *X509CertificateData `tfsdk:"x509_certificate_data"`
+}
+
+func (me *ManagedEndpoint) toAPI(ctx context.Context) (v20230301.ManagedEndpoint, diag.Diagnostics) {
+	x509, diags := me.X509CertificateData.toAPI(ctx)
+	ssh, d := me.SSHCertificateData.toAPI(ctx)
+	diags.Append(d...)
+
+	return v20230301.ManagedEndpoint{
+		Id:                      me.ID.ValueStringPointer(),
+		EndpointConfigurationID: me.EndpointConfigurationID.ValueString(),
+		X509CertificateData:     x509,
+		SshCertificateData:      ssh,
+	}, diags
+}
+
+type SSHCertificateData struct {
+	KeyID      types.String `tfsdk:"key_id"`
+	Principals types.Set    `tfsdk:"principals"`
+}
+
+func (ssh *SSHCertificateData) toAPI(ctx context.Context) (*v20230301.EndpointSSHCertificateData, diag.Diagnostics) {
+	if ssh == nil {
+		return nil, diag.Diagnostics{}
+	}
+	var principals []string
+	diags := ssh.Principals.ElementsAs(ctx, &principals, false)
+
+	return &v20230301.EndpointSSHCertificateData{
+		KeyID:      ssh.KeyID.ValueString(),
+		Principals: principals,
+	}, diags
+}
+
+type X509CertificateData struct {
+	CommonName types.String `tfsdk:"common_name"`
+	SANs       types.Set    `tfsdk:"sans"`
+}
+
+func (x509 *X509CertificateData) toAPI(ctx context.Context) (*v20230301.EndpointX509CertificateData, diag.Diagnostics) {
+	if x509 == nil {
+		return nil, diag.Diagnostics{}
+	}
+
+	var sans []string
+	diags := x509.SANs.ElementsAs(ctx, &sans, false)
+
+	return &v20230301.EndpointX509CertificateData{
+		CommonName: x509.CommonName.ValueString(),
+		Sans:       sans,
+	}, diags
+}
+
+func fromAPI(ctx context.Context, mc *v20230301.ManagedConfiguration, state utils.AttributeGetter) (*Model, diag.Diagnostics) {
+	var diags diag.Diagnostics
+
+	model := &Model{
+		ID:                   types.StringValue(utils.Deref(mc.Id)),
+		Name:                 types.StringValue(mc.Name),
+		AgentConfigurationID: types.StringValue(mc.AgentConfigurationID),
+		HostID:               types.StringValue(utils.Deref(mc.HostID)),
+	}
+
+	for i, me := range mc.ManagedEndpoints {
+		p := path.Root("managed_endpoints").AtListIndex(i)
+		ssh, d := fromSSHCertificateData(ctx, me.SshCertificateData, state, p)
+		diags.Append(d...)
+
+		x509, d := fromX509CertificateData(ctx, me.X509CertificateData, state, p)
+		diags.Append(d...)
+
+		model.ManagedEndpoints = append(model.ManagedEndpoints, ManagedEndpoint{
+			ID:                      types.StringValue(utils.Deref(me.Id)),
+			EndpointConfigurationID: types.StringValue(me.EndpointConfigurationID),
+			SSHCertificateData:      ssh,
+			X509CertificateData:     x509,
+		})
+	}
+
+	return model, diags
+}
+
+func fromSSHCertificateData(ctx context.Context, ssh *v20230301.EndpointSSHCertificateData, state utils.AttributeGetter, p path.Path) (*SSHCertificateData, diag.Diagnostics) {
+	if ssh == nil {
+		return nil, diag.Diagnostics{}
+	}
+
+	values := make([]attr.Value, len(ssh.Principals))
+	for i, s := range ssh.Principals {
+		values[i] = types.StringValue(s)
+	}
+	principals, diags := types.SetValue(types.StringType, values)
+
+	return &SSHCertificateData{
+		KeyID:      types.StringValue(ssh.KeyID),
+		Principals: principals,
+	}, diags
+}
+
+func fromX509CertificateData(ctx context.Context, x509 *v20230301.EndpointX509CertificateData, state utils.AttributeGetter, p path.Path) (*X509CertificateData, diag.Diagnostics) {
+	if x509 == nil {
+		return nil, diag.Diagnostics{}
+	}
+
+	values := make([]attr.Value, len(x509.Sans))
+	for i, s := range x509.Sans {
+		values[i] = types.StringValue(s)
+	}
+	sans, diags := types.SetValue(types.StringType, values)
+
+	return &X509CertificateData{
+		CommonName: types.StringValue(x509.CommonName),
+		SANs:       sans,
+	}, diags
+}
diff --git a/internal/provider/managed_configuration/resource.go b/internal/provider/managed_configuration/resource.go
new file mode 100644
index 0000000000000000000000000000000000000000..ec2e765ea3d1b139d982d93cb94ab729f573bbed
--- /dev/null
+++ b/internal/provider/managed_configuration/resource.go
@@ -0,0 +1,340 @@
+package managed_configuration
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+
+	"github.com/hashicorp/terraform-plugin-framework/path"
+	"github.com/hashicorp/terraform-plugin-framework/resource"
+	"github.com/hashicorp/terraform-plugin-framework/resource/schema"
+	"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
+	"github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier"
+	"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
+	"github.com/hashicorp/terraform-plugin-framework/types"
+	"github.com/hashicorp/terraform-plugin-log/tflog"
+	v20230301 "github.com/smallstep/terraform-provider-smallstep/internal/apiclient/v20230301"
+	"github.com/smallstep/terraform-provider-smallstep/internal/provider/utils"
+)
+
+var _ resource.ResourceWithImportState = (*Resource)(nil)
+
+func NewResource() resource.Resource {
+	return &Resource{}
+}
+
+// Resource defines the resource implementation.
+type Resource struct {
+	client *v20230301.Client
+}
+
+func (r *Resource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+	resp.TypeName = typeName
+}
+
+// Configure adds the Smallstep API client to the resource.
+func (r *Resource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+	// Prevent panic if the provider has not been configured.
+	if req.ProviderData == nil {
+		return
+	}
+
+	client, ok := req.ProviderData.(*v20230301.Client)
+
+	if !ok {
+		resp.Diagnostics.AddError(
+			"Get Smallstep API client from provider",
+			fmt.Sprintf("Expected *v20230301.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
+		)
+		return
+	}
+
+	r.client = client
+}
+
+func (r *Resource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
+	state := &Model{}
+
+	resp.Diagnostics.Append(req.State.Get(ctx, state)...)
+
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	id := state.ID.ValueString()
+
+	httpResp, err := r.client.GetManagedConfiguration(ctx, id, &v20230301.GetManagedConfigurationParams{})
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to read managed configuration %q: %v", id, err),
+		)
+		return
+	}
+	defer httpResp.Body.Close()
+
+	if httpResp.StatusCode == http.StatusNotFound {
+		resp.State.RemoveResource(ctx)
+		return
+	}
+
+	if httpResp.StatusCode != http.StatusOK {
+		resp.Diagnostics.AddError(
+			"Smallstep API Response Error",
+			fmt.Sprintf("Received status %d reading managed configuration %q: %s", httpResp.StatusCode, id, utils.APIErrorMsg(httpResp.Body)),
+		)
+		return
+	}
+
+	ac := &v20230301.ManagedConfiguration{}
+	if err := json.NewDecoder(httpResp.Body).Decode(ac); err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to unmarshal managed configuration %q: %v", id, err),
+		)
+		return
+	}
+
+	remote, d := fromAPI(ctx, ac, req.State)
+	if d.HasError() {
+		resp.Diagnostics.Append(d...)
+		return
+	}
+
+	tflog.Trace(ctx, fmt.Sprintf("read managed configuration %q resource", id))
+
+	resp.Diagnostics.Append(resp.State.Set(ctx, &remote)...)
+}
+
+func (r *Resource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+	component, props, err := utils.Describe("managedConfiguration")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	me, meProps, err := utils.Describe("managedEndpoint")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	x509, x509Props, err := utils.Describe("endpointX509CertificateData")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	ssh, sshProps, err := utils.Describe("endpointSSHCertificateData")
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Parse Smallstep OpenAPI spec",
+			err.Error(),
+		)
+		return
+	}
+
+	resp.Schema = schema.Schema{
+		MarkdownDescription: component,
+
+		Attributes: map[string]schema.Attribute{
+			"id": schema.StringAttribute{
+				MarkdownDescription: props["id"],
+				Computed:            true,
+				PlanModifiers: []planmodifier.String{
+					stringplanmodifier.UseStateForUnknown(),
+				},
+			},
+			"name": schema.StringAttribute{
+				MarkdownDescription: props["name"],
+				Required:            true,
+				PlanModifiers: []planmodifier.String{
+					stringplanmodifier.RequiresReplace(),
+				},
+			},
+			"agent_configuration_id": schema.StringAttribute{
+				MarkdownDescription: props["agentConfigurationID"],
+				Required:            true,
+				PlanModifiers: []planmodifier.String{
+					stringplanmodifier.RequiresReplace(),
+				},
+			},
+			"host_id": schema.StringAttribute{
+				MarkdownDescription: props["hostID"],
+				Optional:            true,
+				Computed:            true,
+				PlanModifiers: []planmodifier.String{
+					stringplanmodifier.RequiresReplace(),
+				},
+			},
+			"managed_endpoints": schema.SetNestedAttribute{
+				MarkdownDescription: me,
+				Required:            true,
+				PlanModifiers: []planmodifier.Set{
+					setplanmodifier.RequiresReplace(),
+				},
+				NestedObject: schema.NestedAttributeObject{
+					Attributes: map[string]schema.Attribute{
+						"id": schema.StringAttribute{
+							MarkdownDescription: meProps["id"],
+							Computed:            true,
+							PlanModifiers: []planmodifier.String{
+								stringplanmodifier.UseStateForUnknown(),
+							},
+						},
+						"endpoint_configuration_id": schema.StringAttribute{
+							MarkdownDescription: meProps["endpointConfigurationID"],
+							Required:            true,
+							PlanModifiers: []planmodifier.String{
+								stringplanmodifier.RequiresReplace(),
+							},
+						},
+						"x509_certificate_data": schema.SingleNestedAttribute{
+							MarkdownDescription: x509,
+							Optional:            true,
+							Attributes: map[string]schema.Attribute{
+								"common_name": schema.StringAttribute{
+									MarkdownDescription: x509Props["commonName"],
+									Required:            true,
+									PlanModifiers: []planmodifier.String{
+										stringplanmodifier.RequiresReplace(),
+									},
+								},
+								"sans": schema.SetAttribute{
+									ElementType:         types.StringType,
+									MarkdownDescription: x509Props["sans"],
+									Required:            true,
+									PlanModifiers: []planmodifier.Set{
+										setplanmodifier.RequiresReplace(),
+									},
+								},
+							},
+						},
+						"ssh_certificate_data": schema.SingleNestedAttribute{
+							MarkdownDescription: ssh,
+							Optional:            true,
+							Attributes: map[string]schema.Attribute{
+								"key_id": schema.StringAttribute{
+									MarkdownDescription: sshProps["keyID"],
+									Required:            true,
+									PlanModifiers: []planmodifier.String{
+										stringplanmodifier.RequiresReplace(),
+									},
+								},
+								"principals": schema.SetAttribute{
+									ElementType:         types.StringType,
+									MarkdownDescription: sshProps["principals"],
+									Required:            true,
+									PlanModifiers: []planmodifier.Set{
+										setplanmodifier.RequiresReplace(),
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+}
+
+func (a *Resource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
+	var plan Model
+
+	resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
+
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	reqBody, diags := plan.toAPI(ctx)
+	resp.Diagnostics.Append(diags...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	httpResp, err := a.client.PostManagedConfigurations(ctx, &v20230301.PostManagedConfigurationsParams{}, reqBody)
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to create managed configuration %q: %v", plan.Name.ValueString(), err),
+		)
+		return
+	}
+	defer httpResp.Body.Close()
+
+	if httpResp.StatusCode != http.StatusCreated {
+		resp.Diagnostics.AddError(
+			"Smallstep API Response Error",
+			fmt.Sprintf("Received status %d creating managed configuration %q: %s", httpResp.StatusCode, plan.Name.ValueString(), utils.APIErrorMsg(httpResp.Body)),
+		)
+		return
+	}
+
+	mc := &v20230301.ManagedConfiguration{}
+	if err := json.NewDecoder(httpResp.Body).Decode(mc); err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to unmarshal managed configuration %q: %v", plan.Name.ValueString(), err),
+		)
+		return
+	}
+
+	state, diags := fromAPI(ctx, mc, req.Plan)
+	resp.Diagnostics.Append(diags...)
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	tflog.Trace(ctx, fmt.Sprintf("create managed configuration %q resource", plan.Name.ValueString()))
+
+	resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
+}
+
+func (r *Resource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
+	// Update not supported. All changes require replacement.
+}
+
+func (a *Resource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
+	var state Model
+
+	resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+
+	if resp.Diagnostics.HasError() {
+		return
+	}
+
+	id := state.ID.ValueString()
+
+	httpResp, err := a.client.DeleteManagedConfiguration(ctx, id, &v20230301.DeleteManagedConfigurationParams{})
+	if err != nil {
+		resp.Diagnostics.AddError(
+			"Smallstep API Client Error",
+			fmt.Sprintf("Failed to delete managed configuration %s: %v", id, err),
+		)
+		return
+	}
+	defer httpResp.Body.Close()
+
+	if httpResp.StatusCode != http.StatusNoContent {
+		resp.Diagnostics.AddError(
+			"Smallstep API Response Error",
+			fmt.Sprintf("Received status %d deleting managed configuration %s: %s", httpResp.StatusCode, id, utils.APIErrorMsg(httpResp.Body)),
+		)
+		return
+	}
+}
+
+func (r *Resource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+	resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+}
diff --git a/internal/provider/managed_workload_data_source_test.go b/internal/provider/managed_workload_data_source_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..161f6db335a760a1bedb62861ddb4d97dadcf770
--- /dev/null
+++ b/internal/provider/managed_workload_data_source_test.go
@@ -0,0 +1,118 @@
+package provider
+
+import (
+	"fmt"
+	"regexp"
+	"strconv"
+	"testing"
+
+	"github.com/hashicorp/terraform-plugin-testing/helper/resource"
+	"github.com/smallstep/terraform-provider-smallstep/internal/provider/utils"
+)
+
+func TestAccManagedWorkloadDataSource(t *testing.T) {
+	authority := utils.NewAuthority(t)
+	provisioner, _ := utils.NewOIDCProvisioner(t, authority.Id)
+	collection := utils.NewCollection(t)
+	attest := utils.FixAttestationAuthority(t, collection.Slug)
+	ac := utils.NewAgentConfiguration(t, authority.Id, provisioner.Name, *attest.Slug)
+	ec := utils.NewEndpointConfiguration(t, authority.Id, provisioner.Name)
+	mc := utils.NewManagedConfiguration(t, *ac.Id, *ec.Id)
+
+	managedConfig := fmt.Sprintf(`
+data "smallstep_managed_configuration" "mc" {
+	id = %q
+}
+`, *mc.Id)
+
+	resource.Test(t, resource.TestCase{
+		ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+		Steps: []resource.TestStep{
+			{
+				Config: managedConfig,
+				Check: resource.ComposeAggregateTestCheckFunc(
+					resource.TestCheckResourceAttr("data.smallstep_managed_configuration.mc", "id", *mc.Id),
+					resource.TestCheckResourceAttr("data.smallstep_managed_configuration.mc", "agent_configuration_id", *ac.Id),
+					resource.TestCheckResourceAttr("data.smallstep_managed_configuration.mc", "host_id", utils.Deref(mc.HostID)),
+					resource.TestCheckResourceAttr("data.smallstep_managed_configuration.mc", "name", mc.Name),
+					resource.TestCheckResourceAttr("data.smallstep_managed_configuration.mc", "managed_endpoints.#", "1"),
+					resource.TestCheckResourceAttr("data.smallstep_managed_configuration.mc", "managed_endpoints.0.endpoint_configuration_id", *ec.Id),
+					resource.TestCheckResourceAttr("data.smallstep_managed_configuration.mc", "managed_endpoints.0.id", *mc.ManagedEndpoints[0].Id),
+					resource.TestCheckResourceAttr("data.smallstep_managed_configuration.mc", "managed_endpoints.0.x509_certificate_data.common_name", mc.ManagedEndpoints[0].X509CertificateData.CommonName),
+					resource.TestCheckResourceAttr("data.smallstep_managed_configuration.mc", "managed_endpoints.0.x509_certificate_data.sans.#", "1"),
+				),
+			},
+		},
+	})
+
+	agentConfig := fmt.Sprintf(`
+data "smallstep_agent_configuration" "agent" {
+	id = %q
+}
+`, *ac.Id)
+
+	resource.Test(t, resource.TestCase{
+		ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+		Steps: []resource.TestStep{
+			{
+				Config: agentConfig,
+				Check: resource.ComposeAggregateTestCheckFunc(
+					resource.TestCheckResourceAttr("data.smallstep_agent_configuration.agent", "id", *ac.Id),
+					resource.TestCheckResourceAttr("data.smallstep_agent_configuration.agent", "authority_id", authority.Id),
+					resource.TestCheckResourceAttr("data.smallstep_agent_configuration.agent", "name", ac.Name),
+					resource.TestCheckResourceAttr("data.smallstep_agent_configuration.agent", "provisioner_name", ac.Provisioner),
+					resource.TestCheckResourceAttr("data.smallstep_agent_configuration.agent", "attestation_slug", *ac.AttestationSlug),
+				),
+			},
+		},
+	})
+
+	endpointConfig := fmt.Sprintf(`
+data "smallstep_endpoint_configuration" "ep" {
+	id = %q
+}
+`, *ec.Id)
+
+	resource.Test(t, resource.TestCase{
+		ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+		Steps: []resource.TestStep{
+			{
+				Config: endpointConfig,
+				Check: resource.ComposeAggregateTestCheckFunc(
+					resource.TestMatchResourceAttr("data.smallstep_endpoint_configuration.ep", "id", regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "authority_id", authority.Id),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "name", ec.Name),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "provisioner_name", ec.Provisioner),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "certificate_info.type", string(ec.CertificateInfo.Type)),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "certificate_info.duration", utils.Deref(ec.CertificateInfo.Duration)),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "certificate_info.crt_file", utils.Deref(ec.CertificateInfo.CrtFile)),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "certificate_info.root_file", utils.Deref(ec.CertificateInfo.RootFile)),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "certificate_info.key_file", utils.Deref(ec.CertificateInfo.KeyFile)),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "certificate_info.uid", strconv.Itoa(utils.Deref(ec.CertificateInfo.Uid))),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "certificate_info.gid", strconv.Itoa(utils.Deref(ec.CertificateInfo.Gid))),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "certificate_info.mode", strconv.Itoa(utils.Deref(ec.CertificateInfo.Mode))),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "hooks.sign.shell", utils.Deref(ec.Hooks.Sign.Shell)),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "hooks.sign.before.#", "1"),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "hooks.sign.before.0", (*ec.Hooks.Sign.Before)[0]),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "hooks.sign.after.#", "1"),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "hooks.sign.after.0", (*ec.Hooks.Sign.After)[0]),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "hooks.sign.on_error.#", "1"),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "hooks.sign.on_error.0", (*ec.Hooks.Sign.OnError)[0]),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "hooks.renew.shell", utils.Deref(ec.Hooks.Renew.Shell)),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "hooks.renew.before.#", "1"),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "hooks.renew.before.0", (*ec.Hooks.Renew.Before)[0]),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "hooks.renew.after.#", "1"),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "hooks.renew.after.0", (*ec.Hooks.Renew.After)[0]),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "hooks.renew.on_error.#", "1"),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "hooks.renew.on_error.0", (*ec.Hooks.Renew.OnError)[0]),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "key_info.type", string(utils.Deref(ec.KeyInfo.Type))),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "key_info.format", string(utils.Deref(ec.KeyInfo.Format))),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "key_info.pub_file", utils.Deref(ec.KeyInfo.PubFile)),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "reload_info.method", string(ec.ReloadInfo.Method)),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "reload_info.signal", strconv.Itoa(utils.Deref(ec.ReloadInfo.Signal))),
+					resource.TestCheckResourceAttr("data.smallstep_endpoint_configuration.ep", "reload_info.pid_file", utils.Deref(ec.ReloadInfo.PidFile)),
+				),
+			},
+		},
+	})
+}
diff --git a/internal/provider/managed_workloads_resource_test.go b/internal/provider/managed_workloads_resource_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..1144c8bb954ccb3082a461c10cee47ef69794622
--- /dev/null
+++ b/internal/provider/managed_workloads_resource_test.go
@@ -0,0 +1,308 @@
+package provider
+
+import (
+	"fmt"
+	"regexp"
+	"testing"
+
+	"github.com/google/uuid"
+	"github.com/hashicorp/terraform-plugin-testing/helper/resource"
+	"github.com/smallstep/terraform-provider-smallstep/internal/provider/utils"
+	"github.com/stretchr/testify/require"
+)
+
+func TestAccManagedConfigurationResource(t *testing.T) {
+	attestorRoot, _ := utils.CACerts(t)
+	slug := utils.Slug(t)
+	hostID := uuid.New().String()
+	config := fmt.Sprintf(`
+resource "smallstep_collection" "tpms" {
+	slug = %q
+}
+
+resource "smallstep_collection_instance" "server1" {
+	id = "urn:ek:sha256:RAzbOveN1Y45fYubuTxu5jOXWtOK1HbfZ7yHjBuWlyE="
+	data = "{}"
+	collection_slug = smallstep_collection.tpms.slug
+	depends_on = [smallstep_collection.tpms]
+}
+
+resource "smallstep_attestation_authority" "aa" {
+	name = "tfprovider%s"
+	catalog = smallstep_collection.tpms.slug
+	attestor_roots = %q
+	depends_on = [smallstep_collection.tpms]
+}
+
+resource "smallstep_authority" "agents" {
+	subdomain = %q
+	name = "Agents Authority"
+	type = "devops"
+	admin_emails = ["andrew@smallstep.com"]
+}
+
+resource "smallstep_provisioner" "agents" {
+	authority_id = smallstep_authority.agents.id
+	name = "Agents"
+	type = "ACME_ATTESTATION"
+	acme_attestation = {
+		attestation_formats = ["tpm"]
+		attestation_roots = [smallstep_attestation_authority.aa.root]
+	}
+}
+
+resource "smallstep_provisioner_webhook" "devices" {
+	authority_id = smallstep_authority.agents.id
+	provisioner_id = smallstep_provisioner.agents.id
+	name = "devices"
+	kind = "ENRICHING"
+	cert_type = "X509"
+	server_type = "HOSTED_ATTESTATION"
+	collection_slug = smallstep_collection.tpms.slug
+	depends_on = [smallstep_collection.tpms]
+}
+
+resource "smallstep_agent_configuration" "agent1" {
+	authority_id = smallstep_authority.agents.id
+	provisioner_name = smallstep_provisioner.agents.name
+	name = "Agent1"
+	attestation_slug = smallstep_attestation_authority.aa.slug
+	depends_on = [smallstep_provisioner.agents]
+}
+
+resource "smallstep_endpoint_configuration" "ep1" {
+	name = "My DB"
+	kind = "WORKLOAD"
+
+	# this would generally be a different authority
+	authority_id = smallstep_authority.agents.id
+
+	# this would generally be an x5c provisioner
+	provisioner_name = smallstep_provisioner.agents.name
+
+	certificate_info = {
+		type = "X509"
+		duration = "168h"
+		crt_file = "db.crt"
+		key_file = "db.key"
+		root_file = "ca.crt"
+		uid = 1001
+		gid = 999
+		mode = 256
+	}
+
+	hooks = {
+		renew = {
+			shell = "/bin/sh"
+			before = [
+				"echo renewing 1",
+				"echo renewing 2",
+				"echo renewing 3",
+			]
+			after = [
+				"echo renewed 1",
+				"echo renewew 2",
+				"echo renewed 3",
+			]
+			on_error = [
+				"echo failed renew 1",
+				"echo failed renew 2",
+				"echo failed renew 3",
+			]
+		}
+		sign = {
+			shell = "/bin/bash"
+			before = [
+				"echo signing 1",
+				"echo signing 2",
+				"echo signing 3",
+			]
+			after = [
+				"echo signed 1",
+				"echo signed 2",
+				"echo signed 3",
+			]
+			on_error = [
+				"echo failed sign 1",
+				"echo failed sign 2",
+				"echo failed sign 3",
+			]
+		}
+	}
+
+	key_info = {
+		format = "DER"
+		type = "ECDSA_P256"
+		pub_file = "file.csr"
+	}
+
+
+	reload_info = {
+		method = "SIGNAL"
+		pid_file = "db.pid"
+		signal = 1
+	}
+}
+
+resource "smallstep_endpoint_configuration" "ep2" {
+	name = "SSH"
+	kind = "PEOPLE"
+	authority_id = smallstep_authority.agents.id
+	provisioner_name = smallstep_provisioner.agents.name
+	certificate_info = {
+		type = "SSH_USER"
+	}
+
+	key_info = {
+		type = "DEFAULT"
+		format = "DEFAULT"
+	}
+
+	reload_info = {
+		method = "AUTOMATIC"
+	}
+	hooks = {
+		sign = {}
+	}
+}
+
+
+resource "smallstep_managed_configuration" "mc" {
+	agent_configuration_id = smallstep_agent_configuration.agent1.id
+	host_id = %q
+	name = "Foo MC"
+	managed_endpoints = [
+		{
+			endpoint_configuration_id = smallstep_endpoint_configuration.ep1.id
+			x509_certificate_data = {
+				common_name = "db"
+				sans = [
+					"db",
+					"db.default",
+					"db.default.svc",
+					"db.defaulst.svc.cluster.local",
+				]
+			}
+		},
+	]
+}
+
+resource "smallstep_managed_configuration" "mc2" {
+	agent_configuration_id = smallstep_agent_configuration.agent1.id
+	name = "Multiple Endpoints"
+	managed_endpoints = [
+		{
+			endpoint_configuration_id = smallstep_endpoint_configuration.ep1.id
+			x509_certificate_data = {
+				common_name = "db"
+				sans = ["db.internal"]
+			}
+		},
+		{
+			endpoint_configuration_id = smallstep_endpoint_configuration.ep2.id
+			ssh_certificate_data = {
+				key_id = "abc"
+				principals = [
+					"ops",
+					"eng",
+					"sec",
+				]
+			}
+		},
+	]
+}
+`, slug, slug, attestorRoot, slug, hostID)
+
+	resource.Test(t, resource.TestCase{
+		PreCheck: func() {
+			testAccPreCheck(t)
+			require.NoError(t, utils.SweepAttestationAuthorities())
+		},
+		ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+		Steps: []resource.TestStep{
+			{
+				Config: config,
+				Check: resource.ComposeAggregateTestCheckFunc(
+					// agent
+					resource.TestMatchResourceAttr("smallstep_agent_configuration.agent1", "id", regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)),
+					resource.TestMatchResourceAttr("smallstep_agent_configuration.agent1", "authority_id", regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)),
+					resource.TestCheckResourceAttr("smallstep_agent_configuration.agent1", "provisioner_name", "Agents"),
+
+					// x509 endpoint
+					resource.TestMatchResourceAttr("smallstep_endpoint_configuration.ep1", "id", regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "name", "My DB"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "kind", "WORKLOAD"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "certificate_info.type", "X509"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "certificate_info.duration", "168h"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "certificate_info.crt_file", "db.crt"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "certificate_info.key_file", "db.key"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "certificate_info.root_file", "ca.crt"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "certificate_info.uid", "1001"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "certificate_info.gid", "999"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "certificate_info.mode", "256"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "hooks.renew.shell", "/bin/sh"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "hooks.renew.before.#", "3"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "hooks.renew.after.#", "3"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "hooks.renew.on_error.#", "3"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "hooks.sign.shell", "/bin/bash"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "hooks.sign.before.#", "3"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "hooks.sign.after.#", "3"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "hooks.sign.on_error.#", "3"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "key_info.type", "ECDSA_P256"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "key_info.format", "DER"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "key_info.pub_file", "file.csr"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "reload_info.method", "SIGNAL"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "reload_info.pid_file", "db.pid"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep1", "reload_info.signal", "1"),
+					// ssh endpoint
+					resource.TestMatchResourceAttr("smallstep_endpoint_configuration.ep2", "id", regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep2", "name", "SSH"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep2", "kind", "PEOPLE"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep2", "certificate_info.type", "SSH_USER"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep2", "key_info.type", "DEFAULT"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep2", "key_info.format", "DEFAULT"),
+					resource.TestCheckResourceAttr("smallstep_endpoint_configuration.ep2", "reload_info.method", "AUTOMATIC"),
+					// managed configuration
+					resource.TestMatchResourceAttr("smallstep_managed_configuration.mc", "id", regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)),
+					resource.TestCheckResourceAttr("smallstep_managed_configuration.mc", "host_id", hostID),
+					resource.TestCheckResourceAttr("smallstep_managed_configuration.mc", "name", "Foo MC"),
+					resource.TestCheckResourceAttr("smallstep_managed_configuration.mc", "managed_endpoints.#", "1"),
+					resource.TestMatchResourceAttr("smallstep_managed_configuration.mc", "managed_endpoints.0.endpoint_configuration_id", regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)),
+					resource.TestCheckResourceAttr("smallstep_managed_configuration.mc", "managed_endpoints.0.x509_certificate_data.common_name", "db"),
+					resource.TestCheckResourceAttr("smallstep_managed_configuration.mc", "managed_endpoints.0.x509_certificate_data.sans.#", "4"),
+					// managed configuration 2
+					resource.TestMatchResourceAttr("smallstep_managed_configuration.mc2", "id", regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)),
+					resource.TestMatchResourceAttr("smallstep_managed_configuration.mc2", "host_id", regexp.MustCompile(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`)),
+					resource.TestCheckResourceAttr("smallstep_managed_configuration.mc2", "name", "Multiple Endpoints"),
+					resource.TestCheckResourceAttr("smallstep_managed_configuration.mc2", "managed_endpoints.#", "2"),
+				),
+			},
+			{
+				ResourceName:      "smallstep_agent_configuration.agent1",
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+			{
+				ResourceName:            "smallstep_endpoint_configuration.ep1",
+				ImportState:             true,
+				ImportStateVerify:       true,
+				ImportStateVerifyIgnore: []string{"certificate_info.duration"},
+			},
+			{
+				ResourceName:      "smallstep_endpoint_configuration.ep2",
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+			{
+				ResourceName:      "smallstep_managed_configuration.mc",
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+			{
+				ResourceName:      "smallstep_managed_configuration.mc2",
+				ImportState:       true,
+				ImportStateVerify: true,
+			},
+		},
+	})
+}
diff --git a/internal/provider/provider.go b/internal/provider/provider.go
index d8d984e903608af95cb42994bdd2ecd0a1d8ddd4..d364ea9b40bb664f4a60931628215303dd51c9ed 100644
--- a/internal/provider/provider.go
+++ b/internal/provider/provider.go
@@ -13,10 +13,13 @@ import (
 	"github.com/hashicorp/terraform-plugin-framework/resource"
 	"github.com/hashicorp/terraform-plugin-framework/types"
 	v20230301 "github.com/smallstep/terraform-provider-smallstep/internal/apiclient/v20230301"
+	"github.com/smallstep/terraform-provider-smallstep/internal/provider/agent_configuration"
 	"github.com/smallstep/terraform-provider-smallstep/internal/provider/attestation_authority"
 	"github.com/smallstep/terraform-provider-smallstep/internal/provider/authority"
 	"github.com/smallstep/terraform-provider-smallstep/internal/provider/collection"
 	"github.com/smallstep/terraform-provider-smallstep/internal/provider/collection_instance"
+	"github.com/smallstep/terraform-provider-smallstep/internal/provider/endpoint_configuration"
+	"github.com/smallstep/terraform-provider-smallstep/internal/provider/managed_configuration"
 	"github.com/smallstep/terraform-provider-smallstep/internal/provider/provisioner"
 	"github.com/smallstep/terraform-provider-smallstep/internal/provider/webhook"
 )
@@ -172,6 +175,9 @@ func (p *SmallstepProvider) Resources(ctx context.Context) []func() resource.Res
 		collection_instance.NewResource,
 		attestation_authority.NewResource,
 		webhook.NewResource,
+		agent_configuration.NewResource,
+		endpoint_configuration.NewResource,
+		managed_configuration.NewResource,
 	}
 }
 
@@ -183,6 +189,9 @@ func (p *SmallstepProvider) DataSources(ctx context.Context) []func() datasource
 		collection_instance.NewDataSource,
 		attestation_authority.NewDataSource,
 		webhook.NewDataSource,
+		agent_configuration.NewDataSource,
+		endpoint_configuration.NewDataSource,
+		managed_configuration.NewDataSource,
 	}
 }
 
diff --git a/internal/provider/utils/testutils.go b/internal/provider/utils/testutils.go
index 5b5df5d56fa14079d5822ad56ae496cee93a16bc..5ed872ab30d4397597fbee6d98b3de5e3b201c5d 100644
--- a/internal/provider/utils/testutils.go
+++ b/internal/provider/utils/testutils.go
@@ -294,7 +294,8 @@ func SweepAttestationAuthorities() error {
 	}
 
 	for _, aa := range list {
-		if !strings.HasPrefix(aa.Name, "tfprovider") {
+		// API e2e tests create one named "foo"
+		if !strings.HasPrefix(aa.Name, "tfprovider") && aa.Name != "foo" {
 			continue
 		}
 		resp, err := client.DeleteAttestationAuthority(context.Background(), *aa.Id, &v20230301.DeleteAttestationAuthorityParams{})