Skip to content

Commit 0b0447b

Browse files
authored
feat: support checkly_client_certificate resources [sc-23397] (#307)
* feat: support checkly_client_certificate resources * chore: bump SDK to latest version with client certificate support
1 parent d5c69ec commit 0b0447b

File tree

7 files changed

+339
-3
lines changed

7 files changed

+339
-3
lines changed

checkly/provider.go

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ func Provider() *schema.Provider {
4343
"checkly_trigger_group": resourceTriggerGroup(),
4444
"checkly_environment_variable": resourceEnvironmentVariable(),
4545
"checkly_private_location": resourcePrivateLocation(),
46+
"checkly_client_certificate": resourceClientCertificate(),
4647
},
4748
DataSourcesMap: map[string]*schema.Resource{
4849
"checkly_static_ips": dataSourceStaticIPs(),
+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package checkly
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9+
10+
checkly "github.com/checkly/checkly-go-sdk"
11+
)
12+
13+
func resourceClientCertificate() *schema.Resource {
14+
return &schema.Resource{
15+
Create: resourceClientCertificateCreate,
16+
Read: resourceClientCertificateRead,
17+
Delete: resourceClientCertificateDelete,
18+
Importer: &schema.ResourceImporter{
19+
StateContext: schema.ImportStatePassthroughContext,
20+
},
21+
Description: "Use client certificates to authenticate your API " +
22+
"checks to APIs that require mutual TLS (mTLS) authentication, " +
23+
"or any other authentication scheme where the requester needs to " +
24+
"provide a certificate." +
25+
"\n\n" +
26+
"Each client certificate is specific to a domain name, e.g. " +
27+
"`acme.com` and will be used automatically by any API checks " +
28+
"targeting that domain." +
29+
"\n\n" +
30+
"Changing the value of any attribute forces a new resource to " +
31+
"be created.",
32+
Schema: map[string]*schema.Schema{
33+
"host": {
34+
Type: schema.TypeString,
35+
Required: true,
36+
ForceNew: true,
37+
Description: "The host domain that the certificate should be used for.",
38+
},
39+
"certificate": {
40+
Type: schema.TypeString,
41+
Required: true,
42+
ForceNew: true,
43+
Description: "The client certificate in PEM format.",
44+
},
45+
"private_key": {
46+
Type: schema.TypeString,
47+
Required: true,
48+
ForceNew: true,
49+
Description: "The private key for the certificate in PEM format.",
50+
},
51+
"passphrase": {
52+
Type: schema.TypeString,
53+
Optional: true,
54+
ForceNew: true,
55+
Sensitive: true,
56+
Description: "Passphrase for the private key.",
57+
},
58+
"trusted_ca": {
59+
Type: schema.TypeString,
60+
Optional: true,
61+
ForceNew: true,
62+
Description: "PEM formatted bundle of CA certificates that the client should trust. The bundle may contain many CA certificates.",
63+
},
64+
},
65+
}
66+
}
67+
68+
func resourceClientCertificateCreate(d *schema.ResourceData, client interface{}) error {
69+
clientCertificate, err := clientCertificateFromResourceData(d)
70+
if err != nil {
71+
return fmt.Errorf("resourceClientCertificateCreate: translation error: %w", err)
72+
}
73+
ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout())
74+
defer cancel()
75+
result, err := client.(checkly.Client).CreateClientCertificate(ctx, clientCertificate)
76+
if err != nil {
77+
return fmt.Errorf("CreateClientCertificate: API error: %w", err)
78+
}
79+
d.SetId(result.ID)
80+
return resourceClientCertificateRead(d, client)
81+
}
82+
83+
func clientCertificateFromResourceData(d *schema.ResourceData) (checkly.ClientCertificate, error) {
84+
return checkly.ClientCertificate{
85+
ID: d.Id(),
86+
Host: d.Get("host").(string),
87+
Certificate: d.Get("certificate").(string),
88+
PrivateKey: d.Get("private_key").(string),
89+
Passphrase: d.Get("passphrase").(string),
90+
TrustedCA: d.Get("trusted_ca").(string),
91+
}, nil
92+
}
93+
94+
func resourceDataFromClientCertificate(c *checkly.ClientCertificate, d *schema.ResourceData) error {
95+
d.Set("host", c.Host)
96+
d.Set("certificate", c.Certificate)
97+
d.Set("private_key", c.PrivateKey)
98+
d.Set("trusted_ca", c.TrustedCA)
99+
// The backend does not return a value for Passphrase and even it did, the
100+
// value would be encrypted. Thus, the value should never be modified.
101+
return nil
102+
}
103+
104+
func resourceClientCertificateRead(d *schema.ResourceData, client interface{}) error {
105+
ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout())
106+
defer cancel()
107+
clientCertificate, err := client.(checkly.Client).GetClientCertificate(ctx, d.Id())
108+
if err != nil {
109+
if strings.Contains(err.Error(), "404") {
110+
//if resource is deleted remotely, then mark it as
111+
//successfully gone by unsetting it's ID
112+
d.SetId("")
113+
return nil
114+
}
115+
return fmt.Errorf("resourceClientCertificateRead: API error: %w", err)
116+
}
117+
return resourceDataFromClientCertificate(clientCertificate, d)
118+
}
119+
120+
func resourceClientCertificateDelete(d *schema.ResourceData, client interface{}) error {
121+
ctx, cancel := context.WithTimeout(context.Background(), apiCallTimeout())
122+
defer cancel()
123+
err := client.(checkly.Client).DeleteClientCertificate(ctx, d.Id())
124+
if err != nil {
125+
return fmt.Errorf("resourceClientCertificateDelete: API error: %w", err)
126+
}
127+
return nil
128+
}
+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package checkly
2+
3+
import (
4+
"regexp"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
8+
)
9+
10+
func TestAccClientCertificateCheckRequiredFields(t *testing.T) {
11+
config := `resource "checkly_client_certificate" "test" {}`
12+
accTestCase(t, []resource.TestStep{
13+
{
14+
Config: config,
15+
ExpectError: regexp.MustCompile(`The argument "host" is required`),
16+
},
17+
{
18+
Config: config,
19+
ExpectError: regexp.MustCompile(`The argument "certificate" is required`),
20+
},
21+
{
22+
Config: config,
23+
ExpectError: regexp.MustCompile(`The argument "private_key" is required`),
24+
},
25+
})
26+
}
27+
28+
func TestAccClientCertificate(t *testing.T) {
29+
config := `
30+
resource "checkly_client_certificate" "test" {
31+
host = "*.acme.com"
32+
33+
certificate = <<-EOT
34+
-----BEGIN CERTIFICATE-----
35+
MIICDzCCAbagAwIBAgIUMTZlfGA7WcD8e4/zt2MqxvEgQPYwCgYIKoZIzj0EAwIw
36+
VDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMREwDwYDVQQHDAhUb29udG93bjES
37+
MBAGA1UECgwJQWNtZSBJbmMuMREwDwYDVQQDDAhhY21lLmNvbTAeFw0yNTAzMDMw
38+
NTQ2NTJaFw00OTEwMjMwNTQ2NTJaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD
39+
QTERMA8GA1UEBwwIVG9vbnRvd24xEjAQBgNVBAoMCUFjbWUgSW5jLjEXMBUGA1UE
40+
AwwOV2lsZSBFLiBDb3lvdGUxHDAaBgkqhkiG9w0BCQEWDXdpbGVAYWNtZS5jb20w
41+
WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATAjjDGsKFS1qgdNqziDZoD5hamTfdH
42+
0P+Ukk1RIue57QYVXhQSyNzcEz15kQnwYezEqfN+FtjtTwdk/CgnAELlo0IwQDAd
43+
BgNVHQ4EFgQU9C9CpZqM2WMrOs3vAYsc5GbjyzswHwYDVR0jBBgwFoAUnlOyzF/N
44+
K7YmKQegLdbdyIOCT/UwCgYIKoZIzj0EAwIDRwAwRAIgGgSnBymlH4MkZCVk5DYH
45+
PdnDo2Xf5uFi1Eyn2LTYP1MCIEtiGtsf0qYv6NzIPd5uTTZoB/8hPrAgM1QzWG4O
46+
3C/I
47+
-----END CERTIFICATE-----
48+
EOT
49+
50+
private_key = <<-EOT
51+
-----BEGIN ENCRYPTED PRIVATE KEY-----
52+
MIH0MF8GCSqGSIb3DQEFDTBSMDEGCSqGSIb3DQEFDDAkBBA5yR3aqy8mZD2wQzp1
53+
FH2JAgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBKgQQA49YCnXvfJ2CsQsV
54+
9C5JJwSBkNkWunSlqyeVW6OFa/+OjlLArgTGvW5ul08qu/145O9PO4Nr2CXeK5N2
55+
uvHwkWGfD8IVke+sgZPUjLoHsJ4h4AnyxlNHpIxgOfm0CoXT7PTaFb//d5NC6XyB
56+
K7ZpBzIThGlbuS/b9wp4MPmSaJn5Fci+84VG7KYK5RxU0fcU0rGSBynrZw803wnO
57+
FjP7qaq5bw==
58+
-----END ENCRYPTED PRIVATE KEY-----
59+
EOT
60+
61+
passphrase = "secret password"
62+
63+
trusted_ca = <<-EOT
64+
-----BEGIN CERTIFICATE-----
65+
MIIB/jCCAaOgAwIBAgIUZzxdNpoDYXaNiIBsh0/s++I+ZOEwCgYIKoZIzj0EAwIw
66+
VDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMREwDwYDVQQHDAhUb29udG93bjES
67+
MBAGA1UECgwJQWNtZSBJbmMuMREwDwYDVQQDDAhhY21lLmNvbTAeFw0yNTAzMDMw
68+
NTQzMDZaFw0yNTA0MDIwNTQzMDZaMFQxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD
69+
QTERMA8GA1UEBwwIVG9vbnRvd24xEjAQBgNVBAoMCUFjbWUgSW5jLjERMA8GA1UE
70+
AwwIYWNtZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARDH3KGK6Vsk1A4
71+
yGf9ItQIS3yuAOi0n0ihmPzIOOOEN0c758ETABeUdgH55bakdx6q5KYSxf4TuXsJ
72+
2nCihqVVo1MwUTAdBgNVHQ4EFgQUnlOyzF/NK7YmKQegLdbdyIOCT/UwHwYDVR0j
73+
BBgwFoAUnlOyzF/NK7YmKQegLdbdyIOCT/UwDwYDVR0TAQH/BAUwAwEB/zAKBggq
74+
hkjOPQQDAgNJADBGAiEA/cJ9jV8MQz4ypQsFvUatrnbxyHO0f+pJhf09pAk6Kj8C
75+
IQCkSbope5r0KlVdqBeFF8wCfE3plwpelve3jqVIz6MedQ==
76+
-----END CERTIFICATE-----
77+
EOT
78+
}`
79+
accTestCase(t, []resource.TestStep{
80+
{
81+
Config: config,
82+
Check: resource.ComposeTestCheckFunc(
83+
resource.TestCheckResourceAttr(
84+
"checkly_client_certificate.test",
85+
"host",
86+
"*.acme.com",
87+
),
88+
resource.TestCheckResourceAttr(
89+
"checkly_client_certificate.test",
90+
"certificate",
91+
"-----BEGIN CERTIFICATE-----\n"+
92+
"MIICDzCCAbagAwIBAgIUMTZlfGA7WcD8e4/zt2MqxvEgQPYwCgYIKoZIzj0EAwIw\n"+
93+
"VDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMREwDwYDVQQHDAhUb29udG93bjES\n"+
94+
"MBAGA1UECgwJQWNtZSBJbmMuMREwDwYDVQQDDAhhY21lLmNvbTAeFw0yNTAzMDMw\n"+
95+
"NTQ2NTJaFw00OTEwMjMwNTQ2NTJaMHgxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD\n"+
96+
"QTERMA8GA1UEBwwIVG9vbnRvd24xEjAQBgNVBAoMCUFjbWUgSW5jLjEXMBUGA1UE\n"+
97+
"AwwOV2lsZSBFLiBDb3lvdGUxHDAaBgkqhkiG9w0BCQEWDXdpbGVAYWNtZS5jb20w\n"+
98+
"WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATAjjDGsKFS1qgdNqziDZoD5hamTfdH\n"+
99+
"0P+Ukk1RIue57QYVXhQSyNzcEz15kQnwYezEqfN+FtjtTwdk/CgnAELlo0IwQDAd\n"+
100+
"BgNVHQ4EFgQU9C9CpZqM2WMrOs3vAYsc5GbjyzswHwYDVR0jBBgwFoAUnlOyzF/N\n"+
101+
"K7YmKQegLdbdyIOCT/UwCgYIKoZIzj0EAwIDRwAwRAIgGgSnBymlH4MkZCVk5DYH\n"+
102+
"PdnDo2Xf5uFi1Eyn2LTYP1MCIEtiGtsf0qYv6NzIPd5uTTZoB/8hPrAgM1QzWG4O\n"+
103+
"3C/I\n"+
104+
"-----END CERTIFICATE-----\n",
105+
),
106+
resource.TestCheckResourceAttr(
107+
"checkly_client_certificate.test",
108+
"private_key",
109+
"-----BEGIN ENCRYPTED PRIVATE KEY-----\n"+
110+
"MIH0MF8GCSqGSIb3DQEFDTBSMDEGCSqGSIb3DQEFDDAkBBA5yR3aqy8mZD2wQzp1\n"+
111+
"FH2JAgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBKgQQA49YCnXvfJ2CsQsV\n"+
112+
"9C5JJwSBkNkWunSlqyeVW6OFa/+OjlLArgTGvW5ul08qu/145O9PO4Nr2CXeK5N2\n"+
113+
"uvHwkWGfD8IVke+sgZPUjLoHsJ4h4AnyxlNHpIxgOfm0CoXT7PTaFb//d5NC6XyB\n"+
114+
"K7ZpBzIThGlbuS/b9wp4MPmSaJn5Fci+84VG7KYK5RxU0fcU0rGSBynrZw803wnO\n"+
115+
"FjP7qaq5bw==\n"+
116+
"-----END ENCRYPTED PRIVATE KEY-----\n",
117+
),
118+
resource.TestCheckResourceAttr(
119+
"checkly_client_certificate.test",
120+
"passphrase",
121+
"secret password",
122+
),
123+
resource.TestCheckResourceAttr(
124+
"checkly_client_certificate.test",
125+
"trusted_ca",
126+
"-----BEGIN CERTIFICATE-----\n"+
127+
"MIIB/jCCAaOgAwIBAgIUZzxdNpoDYXaNiIBsh0/s++I+ZOEwCgYIKoZIzj0EAwIw\n"+
128+
"VDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMREwDwYDVQQHDAhUb29udG93bjES\n"+
129+
"MBAGA1UECgwJQWNtZSBJbmMuMREwDwYDVQQDDAhhY21lLmNvbTAeFw0yNTAzMDMw\n"+
130+
"NTQzMDZaFw0yNTA0MDIwNTQzMDZaMFQxCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD\n"+
131+
"QTERMA8GA1UEBwwIVG9vbnRvd24xEjAQBgNVBAoMCUFjbWUgSW5jLjERMA8GA1UE\n"+
132+
"AwwIYWNtZS5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARDH3KGK6Vsk1A4\n"+
133+
"yGf9ItQIS3yuAOi0n0ihmPzIOOOEN0c758ETABeUdgH55bakdx6q5KYSxf4TuXsJ\n"+
134+
"2nCihqVVo1MwUTAdBgNVHQ4EFgQUnlOyzF/NK7YmKQegLdbdyIOCT/UwHwYDVR0j\n"+
135+
"BBgwFoAUnlOyzF/NK7YmKQegLdbdyIOCT/UwDwYDVR0TAQH/BAUwAwEB/zAKBggq\n"+
136+
"hkjOPQQDAgNJADBGAiEA/cJ9jV8MQz4ypQsFvUatrnbxyHO0f+pJhf09pAk6Kj8C\n"+
137+
"IQCkSbope5r0KlVdqBeFF8wCfE3plwpelve3jqVIz6MedQ==\n"+
138+
"-----END CERTIFICATE-----\n",
139+
),
140+
),
141+
},
142+
})
143+
}

docs/resources/client_certificate.md

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "checkly_client_certificate Resource - terraform-provider-checkly"
4+
subcategory: ""
5+
description: |-
6+
Use client certificates to authenticate your API checks to APIs that require mutual TLS (mTLS) authentication, or any other authentication scheme where the requester needs to provide a certificate.
7+
Each client certificate is specific to a domain name, e.g. acme.com and will be used automatically by any API checks targeting that domain.
8+
Changing the value of any attribute forces a new resource to be created.
9+
---
10+
11+
# checkly_client_certificate (Resource)
12+
13+
Use client certificates to authenticate your API checks to APIs that require mutual TLS (mTLS) authentication, or any other authentication scheme where the requester needs to provide a certificate.
14+
15+
Each client certificate is specific to a domain name, e.g. `acme.com` and will be used automatically by any API checks targeting that domain.
16+
17+
Changing the value of any attribute forces a new resource to be created.
18+
19+
## Example Usage
20+
21+
```terraform
22+
variable "acme_client_certificate_passphrase" {
23+
type = string
24+
sensitive = true
25+
}
26+
27+
resource "checkly_client_certificate" "test" {
28+
host = "*.acme.com"
29+
certificate = file("${path.module}/cert.pem")
30+
private_key = file("${path.module}/key.pem")
31+
trusted_ca = file("${path.module}/ca.pem")
32+
passphrase = var.acme_client_certificate_passphrase
33+
}
34+
```
35+
36+
<!-- schema generated by tfplugindocs -->
37+
## Schema
38+
39+
### Required
40+
41+
- `certificate` (String) The client certificate in PEM format.
42+
- `host` (String) The host domain that the certificate should be used for.
43+
- `private_key` (String) The private key for the certificate in PEM format.
44+
45+
### Optional
46+
47+
- `passphrase` (String, Sensitive) Passphrase for the private key.
48+
- `trusted_ca` (String) PEM formatted bundle of CA certificates that the client should trust. The bundle may contain many CA certificates.
49+
50+
### Read-Only
51+
52+
- `id` (String) The ID of this resource.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
variable "acme_client_certificate_passphrase" {
2+
type = string
3+
sensitive = true
4+
}
5+
6+
resource "checkly_client_certificate" "test" {
7+
host = "*.acme.com"
8+
certificate = file("${path.module}/cert.pem")
9+
private_key = file("${path.module}/key.pem")
10+
trusted_ca = file("${path.module}/ca.pem")
11+
passphrase = var.acme_client_certificate_passphrase
12+
}

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.22
44

55
require (
66
github.com/aws/aws-sdk-go v1.44.122 // indirect
7-
github.com/checkly/checkly-go-sdk v1.9.1
7+
github.com/checkly/checkly-go-sdk v1.10.0
88
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
99
github.com/google/go-cmp v0.6.0
1010
github.com/gruntwork-io/terratest v0.41.16

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,8 @@ github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTS
240240
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
241241
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
242242
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
243-
github.com/checkly/checkly-go-sdk v1.9.1 h1:eXDdrDJsp/xDd3mdEIJOlZvwtCW6VXBEYQmeDzlCWic=
244-
github.com/checkly/checkly-go-sdk v1.9.1/go.mod h1:Pd6tBOggAe41NnCU5KwqA8JvD6J20/IctszT2E0AvHo=
243+
github.com/checkly/checkly-go-sdk v1.10.0 h1:8TtVf0nwKzDRKHhZmb+8bFYLcgzE1K4vxgVy/XOgOyg=
244+
github.com/checkly/checkly-go-sdk v1.10.0/go.mod h1:Pd6tBOggAe41NnCU5KwqA8JvD6J20/IctszT2E0AvHo=
245245
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
246246
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
247247
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=

0 commit comments

Comments
 (0)