From 672b916e7bfb40c0929432b58db628706c3ed5dc Mon Sep 17 00:00:00 2001 From: Francis Chuang Date: Thu, 3 Aug 2023 19:38:45 +1000 Subject: [PATCH] Add support for openssh comments when generating a private key --- .../ENHANCEMENTS-20230803-094241.yaml | 6 +++++ docs/data-sources/public_key.md | 1 + docs/resources/private_key.md | 1 + internal/provider/common_key.go | 10 +++++++ internal/provider/data_source_public_key.go | 4 +++ internal/provider/models.go | 1 + internal/provider/resource_private_key.go | 14 ++++++++-- .../provider/resource_private_key_test.go | 26 +++++++++++++++++++ 8 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 .changes/unreleased/ENHANCEMENTS-20230803-094241.yaml diff --git a/.changes/unreleased/ENHANCEMENTS-20230803-094241.yaml b/.changes/unreleased/ENHANCEMENTS-20230803-094241.yaml new file mode 100644 index 00000000..3d8e9675 --- /dev/null +++ b/.changes/unreleased/ENHANCEMENTS-20230803-094241.yaml @@ -0,0 +1,6 @@ +kind: ENHANCEMENTS +body: 'resource/tls_private_key: add openssh_comment attribute + data-source/tls_public_key: add openssh_comment attribute' +time: 2023-08-03T09:42:41.390232535Z +custom: + Issue: "395" diff --git a/docs/data-sources/public_key.md b/docs/data-sources/public_key.md index 0b8541d1..9451cc6a 100644 --- a/docs/data-sources/public_key.md +++ b/docs/data-sources/public_key.md @@ -43,6 +43,7 @@ data "tls_public_key" "private_key_openssh-example" { - `algorithm` (String) The name of the algorithm used by the given private key. Possible values are: `RSA`, `ECDSA`, `ED25519`. - `id` (String) Unique identifier for this data source: hexadecimal representation of the SHA1 checksum of the data source. +- `openssh_comment` (String) The OpenSSH comment. - `public_key_fingerprint_md5` (String) The fingerprint of the public key data in OpenSSH MD5 hash format, e.g. `aa:bb:cc:...`. Only available if the selected private key format is compatible, as per the rules for `public_key_openssh` and [ECDSA P224 limitations](../../docs#limitations). - `public_key_fingerprint_sha256` (String) The fingerprint of the public key data in OpenSSH SHA256 hash format, e.g. `SHA256:...`. Only available if the selected private key format is compatible, as per the rules for `public_key_openssh` and [ECDSA P224 limitations](../../docs#limitations). - `public_key_openssh` (String) The public key, in [OpenSSH PEM (RFC 4716)](https://datatracker.ietf.org/doc/html/rfc4716) format. This is also known as ['Authorized Keys'](https://www.ssh.com/academy/ssh/authorized_keys/openssh#format-of-the-authorized-keys-file) format. This is not populated for `ECDSA` with curve `P224`, as it is [not supported](../../docs#limitations). **NOTE**: the [underlying](https://pkg.go.dev/encoding/pem#Encode) [libraries](https://pkg.go.dev/golang.org/x/crypto/ssh#MarshalAuthorizedKey) that generate this value append a `\n` at the end of the PEM. In case this disrupts your use case, we recommend using [`trimspace()`](https://www.terraform.io/language/functions/trimspace). diff --git a/docs/resources/private_key.md b/docs/resources/private_key.md index 941b0d4c..a2fd6636 100644 --- a/docs/resources/private_key.md +++ b/docs/resources/private_key.md @@ -53,6 +53,7 @@ resource "tls_private_key" "ed25519-example" { ### Optional - `ecdsa_curve` (String) When `algorithm` is `ECDSA`, the name of the elliptic curve to use. Currently-supported values are: `P224`, `P256`, `P384`, `P521`. (default: `P224`). +- `openssh_comment` (String) Comment to add to the OpenSSH key (default: `""`). - `rsa_bits` (Number) When `algorithm` is `RSA`, the size of the generated RSA key, in bits (default: `2048`). ### Read-Only diff --git a/internal/provider/common_key.go b/internal/provider/common_key.go index 460b4444..8e3e42af 100644 --- a/internal/provider/common_key.go +++ b/internal/provider/common_key.go @@ -14,6 +14,7 @@ import ( "crypto/x509" "encoding/pem" "fmt" + "strings" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" @@ -201,6 +202,15 @@ func setPublicKeyAttributes(ctx context.Context, s *tfsdk.State, prvKey crypto.P sshPubKeyBytes := ssh.MarshalAuthorizedKey(sshPubKey) pubKeySSH = string(sshPubKeyBytes) + + // Manually add the comment as MarshalAuthorizedKeys ignores it: https://github.com/golang/go/issues/46870 + var comment *string + diags.Append(s.GetAttribute(ctx, path.Root("openssh_comment"), &comment)...) + + if comment != nil { + pubKeySSH = fmt.Sprintf("%s %s\n", strings.TrimSuffix(pubKeySSH, "\n"), *comment) + } + pubKeySSHFingerprintMD5 = ssh.FingerprintLegacyMD5(sshPubKey) pubKeySSHFingerprintSHA256 = ssh.FingerprintSHA256(sshPubKey) } diff --git a/internal/provider/data_source_public_key.go b/internal/provider/data_source_public_key.go index 6d4eb522..0f8d879b 100644 --- a/internal/provider/data_source_public_key.go +++ b/internal/provider/data_source_public_key.go @@ -69,6 +69,10 @@ func (d *publicKeyDataSource) Schema(ctx context.Context, req datasource.SchemaR Description: "The name of the algorithm used by the given private key. " + fmt.Sprintf("Possible values are: `%s`. ", strings.Join(supportedAlgorithmsStr(), "`, `")), }, + "openssh_comment": schema.StringAttribute{ + Computed: true, + Description: "The OpenSSH comment.", + }, "public_key_pem": schema.StringAttribute{ Computed: true, Description: "The public key, in [PEM (RFC 1421)](https://datatracker.ietf.org/doc/html/rfc1421) format. " + diff --git a/internal/provider/models.go b/internal/provider/models.go index 474f888d..be416413 100644 --- a/internal/provider/models.go +++ b/internal/provider/models.go @@ -65,6 +65,7 @@ type certificateSubjectModel struct { type privateKeyResourceModel struct { Algorithm types.String `tfsdk:"algorithm"` + OpenSSHComment types.String `tfsdk:"openssh_comment"` RSABits types.Int64 `tfsdk:"rsa_bits"` ECDSACurve types.String `tfsdk:"ecdsa_curve"` PrivateKeyPem types.String `tfsdk:"private_key_pem"` diff --git a/internal/provider/resource_private_key.go b/internal/provider/resource_private_key.go index b1a8b4a2..4a86fd60 100644 --- a/internal/provider/resource_private_key.go +++ b/internal/provider/resource_private_key.go @@ -61,6 +61,13 @@ func (r *privateKeyResource) Schema(_ context.Context, req resource.SchemaReques }, // Optional attributes + "openssh_comment": schema.StringAttribute{ + Optional: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + MarkdownDescription: "Comment to add to the OpenSSH key (default: `\"\"`).", + }, "rsa_bits": schema.Int64Attribute{ Optional: true, Computed: true, @@ -159,6 +166,10 @@ func privateKeyResourceSchemaV1() schema.Schema { }, // Optional attributes + "openssh_comment": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Comment to add to the OpenSSH key (default: `\"\"`).", + }, "rsa_bits": schema.Int64Attribute{ Optional: true, Computed: true, @@ -318,12 +329,11 @@ func (r *privateKeyResource) Create(ctx context.Context, req resource.CreateRequ tflog.Debug(ctx, "Marshalling private key to OpenSSH PEM (if supported)") newState.PrivateKeyOpenSSH = types.StringValue("") if prvKeySupportsOpenSSHMarshalling(prvKey) { - openSSHKeyPemBlock, err := openssh.MarshalPrivateKey(prvKey, "") + openSSHKeyPemBlock, err := openssh.MarshalPrivateKey(prvKey, newState.OpenSSHComment.ValueString()) if err != nil { res.Diagnostics.AddError("Unable to marshal private key into OpenSSH format", err.Error()) return } - newState.PrivateKeyOpenSSH = types.StringValue(string(pem.EncodeToMemory(openSSHKeyPemBlock))) } diff --git a/internal/provider/resource_private_key_test.go b/internal/provider/resource_private_key_test.go index dd0ee06b..ec80cf8b 100644 --- a/internal/provider/resource_private_key_test.go +++ b/internal/provider/resource_private_key_test.go @@ -401,3 +401,29 @@ func TestAccPrivateKeyED25519_UpgradeFromVersion3_4_0(t *testing.T) { }, }) } + +func TestOpenSSHComment(t *testing.T) { + r.UnitTest(t, r.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []r.TestStep{ + { + Config: ` + resource "tls_private_key" "test" { + algorithm = "ED25519" + openssh_comment = "test@test" + } + `, + Check: r.ComposeAggregateTestCheckFunc( + tu.TestCheckPEMFormat("tls_private_key.test", "private_key_pem", PreamblePrivateKeyPKCS8.String()), + tu.TestCheckPEMFormat("tls_private_key.test", "public_key_pem", PreamblePublicKey.String()), + tu.TestCheckPEMFormat("tls_private_key.test", "private_key_openssh", PreamblePrivateKeyOpenSSH.String()), + tu.TestCheckPEMFormat("tls_private_key.test", "private_key_pem_pkcs8", PreamblePrivateKeyPKCS8.String()), + r.TestMatchResourceAttr("tls_private_key.test", "public_key_openssh", regexp.MustCompile(`^ssh-ed25519 `)), + r.TestMatchResourceAttr("tls_private_key.test", "public_key_openssh", regexp.MustCompile(` test@test\n$`)), + r.TestMatchResourceAttr("tls_private_key.test", "public_key_fingerprint_md5", regexp.MustCompile(`^([abcdef\d]{2}:){15}[abcdef\d]{2}`)), + r.TestMatchResourceAttr("tls_private_key.test", "public_key_fingerprint_sha256", regexp.MustCompile(`^SHA256:`)), + ), + }, + }, + }) +}