Skip to content

Commit

Permalink
add keychain resource (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
wasaga authored Jan 2, 2025
1 parent 1f9ae99 commit f0842e4
Show file tree
Hide file tree
Showing 9 changed files with 286 additions and 1 deletion.
7 changes: 7 additions & 0 deletions example/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,13 @@ resource "pomerium_route" "test_route" {
policies = [pomerium_policy.test_policy.id]
}

resource "pomerium_key_pair" "test_key_pair" {
namespace_id = pomerium_namespace.test_namespace.id
name = "test-key-pair"
certificate = file("test.host.pem")
key = file("test.host-key.pem")
}

# Data source examples
data "pomerium_namespaces" "all_namespaces" {}

Expand Down
28 changes: 28 additions & 0 deletions example/test.host-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDg+iDtSa8iZ2gR
1kVF30lU/vyYvEVxiyQHMWfH3V3dc8fOaVtC3NpIFjDnP2tNCOgiQOq4VjW5uZR6
HNNDdFIq/QXqp3EjPufFPGZGicbEjgHQJvuWM45XdTppbFjngQISaeal+YmsICyQ
278HKfRBkl59yRVmwTwCsyKmJfhgegG/r0TxHA3c4nDuTv2hWpw31lLzS/dt3lIz
8j1oWbbuJBzHk8aiRTYfrqVnsINh1GDjzIff31Pcpw+4nGkkms+V/KcB/eMvyLKt
5jL4vbMfXQgR+76yjUGhiiTkaWrBdDrTZwVbfEyqSbUcH05jB9XWfLLWvgWrh2r2
yLluPXDJAgMBAAECggEBAJ7EpnwO5gOXikAcQOLggvXyxPxc0X0hvpk86oqH1Hg1
/ynR/E+hYIJC9twbS3Qf9wJFYeAZJu0c5IWQ1h7idiJUUdqZtCQ1focY6uyYyqdn
uCvXdvE3DRr3ZfOEPPGTf4zTI6y5/8hzJEBOc+9wUin8S6blQ68ya1FUbf1nCnO6
E4QdS2jOpIYusq/zkPUFjqWKZ16iwwimXtqUSu222lckfsj35W8B5tNsLGP+7pzo
3ew8WtfmMNObSDwAH8jv7qP0P5kfsASVX4TYs17o3nnk1VzGwVZ0Yks70NLpt9Dk
Akw/QQHQJ3h3szUFMUvic2eYZwhkh/xI3vVcva8byCkCgYEA5nF7KNLYzlTWGHAl
30HlL21Di2orEcOXDzqA/h2ewuRnu2bKYhtAYKjmUkCvK3rSlxYY+BvsrnVGP2el
yDye9czRUVT0+837UrHz3kK1AUe97SWUc0s33v1zjRT5Ks+k6dFEc7j5sZdoDMHO
GCjSUFEtOkCEgeBePpq8NOs9sZsCgYEA+e10usmvfzmnIu5mk2S5rM36KN7sIIHD
tIzTWs0u/dXSnvcHVCCZtS/T30SRDeMt9xMnncdrDa0Cc54nhvDGcGLrWdpWmYjO
Ybtn3N3RtknN2RRjfy8FRtWmNRIObch/HegT/lPwI5Prz5T0SUX6aQaOkW9nVSOT
CAlmYVp2b2sCgYBJpcD3thMGNkTKQKVJ6dRmSORKXR7wqXLQsiDhlfPUU4z3bo2F
tzHm8nPRm8yf97vv/2bxfHMy+lX+E5D/Iqim49ONy7oT86u7rRXEVctlYllHvjfo
dQShJp1UXHyZew04kOHwnhBm+n2Nfi7wt7MnahorQM1YeK2GEovN2dS4uwKBgBV4
XrTFKrxlOYR4snsrBgBgWYM1U1efji5ugqGkSssnmUZqtkh13H6CM1NU0pk80PAO
xzJ4tSuhlzpTddXTzVhORyWa6iGabRBcRxnkPGXKhVKGu91rLqrdI8AfYvnvZWwu
SblpgJGt3W4hv7KjvlVTaN/5kAjyW2kvVYo7eT0tAoGBAL5t9AfGQ7Y5tM+HqjcG
R8eBIuXdhIy+/m0SlIUOyyl/02CdbKvgICO112kHPWmB47kAWFMXjwsoNh7A+y+P
CUSZzKQOdtMggwyDNyv3XNzby9ov7NoT8CzFBSySQc6oKTOzhA+QizpWAiwK16oG
cKnHRRbN0v6RFnOgHRFhICJ2
-----END PRIVATE KEY-----
25 changes: 25 additions & 0 deletions example/test.host.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIELjCCApagAwIBAgIQTs0uQ1hCYD73QywOGx4/MTANBgkqhkiG9w0BAQsFADB/
MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExKjAoBgNVBAsMIXBvbWVy
aXVtQERlbmlzcy1NYWNCb29rLVByby5sb2NhbDExMC8GA1UEAwwobWtjZXJ0IHBv
bWVyaXVtQERlbmlzcy1NYWNCb29rLVByby5sb2NhbDAeFw0yNDEyMzEyMDUyNTla
Fw0yNzAzMzExOTUyNTlaMEcxJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9wbWVudCBj
ZXJ0aWZpY2F0ZTEcMBoGA1UECwwTcG9tZXJpdW1ATWFjQm9va1BybzCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAOD6IO1JryJnaBHWRUXfSVT+/Ji8RXGL
JAcxZ8fdXd1zx85pW0Lc2kgWMOc/a00I6CJA6rhWNbm5lHoc00N0Uir9BeqncSM+
58U8ZkaJxsSOAdAm+5Yzjld1OmlsWOeBAhJp5qX5iawgLJDbvwcp9EGSXn3JFWbB
PAKzIqYl+GB6Ab+vRPEcDdzicO5O/aFanDfWUvNL923eUjPyPWhZtu4kHMeTxqJF
Nh+upWewg2HUYOPMh9/fU9ynD7icaSSaz5X8pwH94y/Isq3mMvi9sx9dCBH7vrKN
QaGKJORpasF0OtNnBVt8TKpJtRwfTmMH1dZ8sta+BauHavbIuW49cMkCAwEAAaNe
MFwwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQY
MBaAFBmaz0FaKdgN/THnoaFj3GdhoZQeMBQGA1UdEQQNMAuCCXRlc3QuaG9zdDAN
BgkqhkiG9w0BAQsFAAOCAYEAau36+ae85z31RIqp8Nu3irue/x0SXoOS7pqZGauY
U8LqwilAVunwLj2m3wH6CpW+QSAappiCoqkwf1B+E1YpnTcj2dBkIxXnKQWD0HRz
F91pk8YSYYo0q9H5qlLRIiOWsPqgPM6XHr7Mky91Mlu6hQujwTs0IABKb/JG1m62
WTJlpzGRBKf+jNuEdU20QQrHbsjZ7PpbK4I/arxzqMp2Q7sEZqpvVcIJ+JfumpND
FcS1c5RmJijEI3abXiVJyiXRize+A8fBnJwKpjwZn3LgcDvA4IR0XwFVu/WQn9Np
bisxgd81wSLzworfoL7TFMwJWZWcyNkExWvJG8/8Ppw5XUjvQWKbvyeZnqNQnl2+
sHr6Xm2kCLNYG+LUUKpVWL0wIuY3xi5X5L01Z8OzgolwCBKhaOa9hiGH8bcM0hUY
ZnHAg0EZGQc9cJ2VswT3bq43yZUW2PG4d0YF8WgjWVi0w/LCgv2Nw/Wu+Lh6Z29k
9of5B9wbLSl1LhsGEcqNjWyP
-----END CERTIFICATE-----
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/hashicorp/terraform-plugin-go v0.25.0
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/iancoleman/strcase v0.3.0
github.com/pomerium/enterprise-client-go v0.18.1-0.20241202185750-aab20a674922
github.com/pomerium/enterprise-client-go v0.18.1-0.20241231202145-17196aa57a93
github.com/pomerium/pomerium v0.28.0
github.com/rs/zerolog v1.33.0
github.com/stretchr/testify v1.10.0
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,12 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pomerium/csrf v1.7.0 h1:Qp4t6oyEod3svQtKfJZs589mdUTWKVf7q0PgCKYCshY=
github.com/pomerium/csrf v1.7.0/go.mod h1:hAPZV47mEj2T9xFs+ysbum4l7SF1IdrryYaY6PdoIqw=
github.com/pomerium/enterprise-client-go v0.18.1-0.20241202185616-83189ffe78a8 h1:gUf1q1LuvYP5/IK/BQo1CcLfH1PsDm7SkPaMZQnNQGg=
github.com/pomerium/enterprise-client-go v0.18.1-0.20241202185616-83189ffe78a8/go.mod h1:VdYFbxVuHkC9/CEQBSwAu6k69S5uUkwM7bM708YSZJ8=
github.com/pomerium/enterprise-client-go v0.18.1-0.20241202185750-aab20a674922 h1:On4g0z92DXMKNFHhOhpaUJpj+gobumEQcBw3KeFbuQ8=
github.com/pomerium/enterprise-client-go v0.18.1-0.20241202185750-aab20a674922/go.mod h1:+l5FjwKlXDsHahK6JE4anAqx691G6uN/3s5HBcLkPrQ=
github.com/pomerium/enterprise-client-go v0.18.1-0.20241231202145-17196aa57a93 h1:XBOdbvUcrnFHjpfjRG1oS1g58g7V1IETAaTVjzz/19I=
github.com/pomerium/enterprise-client-go v0.18.1-0.20241231202145-17196aa57a93/go.mod h1:+l5FjwKlXDsHahK6JE4anAqx691G6uN/3s5HBcLkPrQ=
github.com/pomerium/pomerium v0.28.0 h1:oNUw623093ybLB+a/W7fZgJXRlZiLlbwXMWXpdMTs3E=
github.com/pomerium/pomerium v0.28.0/go.mod h1:5kyioARKhZjQhXO9yK7ooVfa97ctk/0XJ2AOhVxdRpM=
github.com/pomerium/protoutil v0.0.0-20240813175624-47b7ac43ff46 h1:NRTg8JOXCxcIA1lAgD74iYud0rbshbWOB3Ou4+Huil8=
Expand Down
11 changes: 11 additions & 0 deletions internal/provider/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,3 +221,14 @@ func GetTFObjectTypes[T any]() (map[string]attr.Type, error) {
}
return tm, nil
}

func ToByteSlice(src types.String) []byte {
if src.IsNull() {
return nil
}
val := src.ValueString()
if val == "" {
return nil
}
return []byte(val)
}
13 changes: 13 additions & 0 deletions internal/provider/key_chain_model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package provider

import (
"github.com/hashicorp/terraform-plugin-framework/types"
)

type KeyPairModel struct {
ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
NamespaceID types.String `tfsdk:"namespace_id"`
Certificate types.String `tfsdk:"certificate"`
Key types.String `tfsdk:"key"`
}
196 changes: 196 additions & 0 deletions internal/provider/keychain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package provider

import (
"context"
"fmt"

"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-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"

client "github.com/pomerium/enterprise-client-go"
"github.com/pomerium/enterprise-client-go/pb"
)

var (
_ resource.Resource = &KeyChainResource{}
_ resource.ResourceWithImportState = &KeyChainResource{}
)

type KeyChainResource struct {
client *client.Client
}

// Update the model alias
type KeyChainResourceModel = KeyPairModel

func NewKeyChainResource() resource.Resource {
return &KeyChainResource{}
}

func (r *KeyChainResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_key_pair"
}

func (r *KeyChainResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "KeyPairs managed by Pomerium.",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"name": schema.StringAttribute{
Required: true,
Description: "Name of the key pair",
},
"namespace_id": schema.StringAttribute{
Required: true,
Description: "ID of the namespace this key pair belongs to",
},
"certificate": schema.StringAttribute{
Required: true,
Description: "PEM encoded certificate",
},
"key": schema.StringAttribute{
Optional: true,
Sensitive: true,
Description: "PEM encoded private key",
},
},
}
}

func (r *KeyChainResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}

client, ok := req.ProviderData.(*client.Client)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *client.Client, got: %T", req.ProviderData),
)
return
}

r.client = client
}

func (r *KeyChainResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan KeyChainResourceModel

resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}

keyPairReq := &pb.CreateKeyPairRequest{
NamespaceId: plan.NamespaceID.ValueString(),
Name: plan.Name.ValueString(),
Format: pb.Format_PEM,
Certificate: []byte(plan.Certificate.ValueString()),
}

if !plan.Key.IsNull() {
keyData := []byte(plan.Key.ValueString())
keyPairReq.Key = keyData
}

respKeyPair, err := r.client.KeyChainService.CreateKeyPair(ctx, keyPairReq)
if err != nil {
resp.Diagnostics.AddError("Error creating key pair", err.Error())
return
}

plan.ID = types.StringValue(respKeyPair.KeyPair.Id)

tflog.Trace(ctx, "Created a key pair", map[string]interface{}{
"id": plan.ID.ValueString(),
})

resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}

func (r *KeyChainResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state KeyChainResourceModel

resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

respKeyPair, err := r.client.KeyChainService.GetKeyPair(ctx, &pb.GetKeyPairRequest{
Id: state.ID.ValueString(),
})
if err != nil {
resp.Diagnostics.AddError("Error reading key pair", err.Error())
return
}

state.ID = types.StringValue(respKeyPair.KeyPair.Id)
state.NamespaceID = types.StringValue(respKeyPair.KeyPair.NamespaceId)
state.Name = types.StringValue(respKeyPair.KeyPair.Name)
state.Certificate = types.StringValue(string(respKeyPair.KeyPair.Certificate))

resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}

func (r *KeyChainResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan KeyChainResourceModel

resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}

fmt := pb.Format_PEM
updateReq := &pb.UpdateKeyPairRequest{
Id: plan.ID.ValueString(),
Name: plan.Name.ValueStringPointer(),
Format: &fmt,
Certificate: []byte(plan.Certificate.ValueString()),
}

if !plan.Key.IsNull() {
updateReq.Key = []byte(plan.Key.ValueString())
}

_, err := r.client.KeyChainService.UpdateKeyPair(ctx, updateReq)
if err != nil {
resp.Diagnostics.AddError("Error updating key pair", err.Error())
return
}

resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}

func (r *KeyChainResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state KeyChainResourceModel

resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

_, err := r.client.KeyChainService.DeleteKeyPair(ctx, &pb.DeleteKeyPairRequest{
Id: state.ID.ValueString(),
})
if err != nil {
resp.Diagnostics.AddError("Error deleting key pair", err.Error())
return
}

resp.State.RemoveResource(ctx)
}

func (r *KeyChainResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ func (p *PomeriumProvider) Resources(_ context.Context) []func() resource.Resour
NewPolicyResource,
NewSettingsResource,
NewServiceAccountResource,
NewKeyChainResource,
}
}

Expand Down

0 comments on commit f0842e4

Please sign in to comment.