0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-08-23 03:48:35 +00:00

feat(provider): add support for private key authentication for SSH (#1076)

* feat(provider): add support for private key authentication for SSH

Also fix bunch of issues with acceptance tests

---------

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
Pavel Boldyrev 2024-03-02 11:10:42 -05:00 committed by GitHub
parent 66ec9f4b9b
commit 2c6d3ad01d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 137 additions and 39 deletions

View File

@ -5,7 +5,6 @@ on:
push: push:
branches: branches:
- main - main
- "release/**"
jobs: jobs:
build: build:
@ -30,9 +29,7 @@ jobs:
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version-file: "go.mod" go-version-file: "go.mod"
cache-dependency-path: | cache-dependency-path: "**/*.sum"
go.sum
tools/go.sum
- name: Get dependencies - name: Get dependencies
if: steps.filter.outputs.go == 'true' if: steps.filter.outputs.go == 'true'
@ -63,9 +60,7 @@ jobs:
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version-file: "go.mod" go-version-file: "go.mod"
cache-dependency-path: | cache-dependency-path: "**/*.sum"
go.sum
tools/go.sum
- name: Get dependencies - name: Get dependencies
if: steps.filter.outputs.go == 'true' if: steps.filter.outputs.go == 'true'
@ -88,9 +83,6 @@ jobs:
terraform: [ 1.6 ] terraform: [ 1.6 ]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
environment: pve-acc environment: pve-acc
concurrency:
group: acceptance
cancel-in-progress: true
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -101,18 +93,11 @@ jobs:
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version-file: "go.mod" go-version-file: "go.mod"
cache-dependency-path: | cache-dependency-path: "**/*.sum"
go.sum
tools/go.sum
- name: Get dependencies - name: Get dependencies
run: go mod download run: go mod download
- name: Setup ssh-agent
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.PROXMOX_VE_SSH_USER_PRIVATE_KEY }}
- uses: hashicorp/setup-terraform@a1502cd9e758c50496cc9ac5308c4843bcd56d36 # v3.0.0 - uses: hashicorp/setup-terraform@a1502cd9e758c50496cc9ac5308c4843bcd56d36 # v3.0.0
with: with:
terraform_version: ${{ matrix.terraform }}.* terraform_version: ${{ matrix.terraform }}.*
@ -122,9 +107,11 @@ jobs:
timeout-minutes: 10 timeout-minutes: 10
env: env:
TF_ACC: 1 TF_ACC: 1
PROXMOX_VE_INSECURE: false
PROXMOX_VE_API_TOKEN: "${{ secrets.PROXMOX_VE_API_TOKEN }}" PROXMOX_VE_API_TOKEN: "${{ secrets.PROXMOX_VE_API_TOKEN }}"
PROXMOX_VE_ENDPOINT: "https://${{ secrets.PROXMOX_VE_HOST }}:8006/" PROXMOX_VE_ENDPOINT: "https://${{ secrets.PROXMOX_VE_HOST }}:8006/"
PROXMOX_VE_SSH_AGENT: false
PROXMOX_VE_SSH_USERNAME: "terraform" PROXMOX_VE_SSH_USERNAME: "terraform"
PROXMOX_VE_SSH_AGENT: true PROXMOX_VE_SSH_PRIVATE_KEY: "${{ secrets.PROXMOX_VE_SSH_PRIVATE_KEY }}"
PROXMOX_VE_INSECURE: false
run: make testacc run: make testacc

View File

@ -114,6 +114,41 @@ The provider does not use OS-specific SSH configuration files, such as `~/.ssh/c
Instead, it uses the SSH protocol directly, and supports the `SSH_AUTH_SOCK` environment variable (or `agent_socket` argument) to connect to the `ssh-agent`. Instead, it uses the SSH protocol directly, and supports the `SSH_AUTH_SOCK` environment variable (or `agent_socket` argument) to connect to the `ssh-agent`.
This allows the provider to use the SSH agent configured by the user, and to support multiple SSH agents running on the same machine. This allows the provider to use the SSH agent configured by the user, and to support multiple SSH agents running on the same machine.
You can find more details on the SSH Agent [here](https://www.digitalocean.com/community/tutorials/ssh-essentials-working-with-ssh-servers-clients-and-keys#adding-your-ssh-keys-to-an-ssh-agent-to-avoid-typing-the-passphrase). You can find more details on the SSH Agent [here](https://www.digitalocean.com/community/tutorials/ssh-essentials-working-with-ssh-servers-clients-and-keys#adding-your-ssh-keys-to-an-ssh-agent-to-avoid-typing-the-passphrase).
The SSH agent authentication takes precedence over the `private_key` and `password` authentication.
### SSH Private Key
In some cases where SSH agent is not available, for example when running Terraform from a Windows machine, or when using a CI/CD pipeline that does not support SSH agent forwarding,
you can use the `private_key` argument in the `ssh` block (or alternatively `PROXMOX_VE_SSH_PRIVATE_KEY` environment variable) to provide the private key for the SSH connection.
The private key must be in PEM format, and can be loaded from a file:
```terraform
provider "proxmox" {
...
ssh {
agent = false
private_key = file("~/.ssh/id_rsa")
}
}
```
Not recommended, but you can also use a heredoc syntax to provide the private key as a string (note that the private key content must not be indented):
```terraform
provider "proxmox" {
...
ssh {
agent = false
private_key = <<EOF
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
<SKIPPED>
DMUWUEaH7yMCKl7uCZ9xAAAAAAECAwQF
-----END OPENSSH PRIVATE KEY-----
}
}
```
### SSH User ### SSH User
@ -317,6 +352,7 @@ In addition to [generic provider arguments](https://www.terraform.io/docs/config
- `password` - (Optional) The password to use for the SSH connection. Defaults to the password used for the Proxmox API connection. Can also be sourced from `PROXMOX_VE_SSH_PASSWORD`. - `password` - (Optional) The password to use for the SSH connection. Defaults to the password used for the Proxmox API connection. Can also be sourced from `PROXMOX_VE_SSH_PASSWORD`.
- `agent` - (Optional) Whether to use the SSH agent for the SSH authentication. Defaults to `false`. Can also be sourced from `PROXMOX_VE_SSH_AGENT`. - `agent` - (Optional) Whether to use the SSH agent for the SSH authentication. Defaults to `false`. Can also be sourced from `PROXMOX_VE_SSH_AGENT`.
- `agent_socket` - (Optional) The path to the SSH agent socket. Defaults to the value of the `SSH_AUTH_SOCK` environment variable. Can also be sourced from `PROXMOX_VE_SSH_AUTH_SOCK`. - `agent_socket` - (Optional) The path to the SSH agent socket. Defaults to the value of the `SSH_AUTH_SOCK` environment variable. Can also be sourced from `PROXMOX_VE_SSH_AUTH_SOCK`.
- `private_key` - (Optional) The private key to use for the SSH connection. Can also be sourced from `PROXMOX_VE_SSH_PRIVATE_KEY`. The private key must be in PEM format.
- `socks5_server` - (Optional) The address of the SOCKS5 proxy server to use for the SSH connection. Can also be sourced from `PROXMOX_VE_SSH_SOCKS5_SERVER`. - `socks5_server` - (Optional) The address of the SOCKS5 proxy server to use for the SSH connection. Can also be sourced from `PROXMOX_VE_SSH_SOCKS5_SERVER`.
- `socks5_username` - (Optional) The username to use for the SOCKS5 proxy server. Can also be sourced from `PROXMOX_VE_SSH_SOCKS5_USERNAME`. - `socks5_username` - (Optional) The username to use for the SOCKS5 proxy server. Can also be sourced from `PROXMOX_VE_SSH_SOCKS5_USERNAME`.
- `socks5_password` - (Optional) The password to use for the SOCKS5 proxy server. Can also be sourced from `PROXMOX_VE_SSH_SOCKS5_PASSWORD`. - `socks5_password` - (Optional) The password to use for the SOCKS5 proxy server. Can also be sourced from `PROXMOX_VE_SSH_SOCKS5_PASSWORD`.

View File

@ -63,6 +63,7 @@ type proxmoxProviderModel struct {
SSH []struct { SSH []struct {
Agent types.Bool `tfsdk:"agent"` Agent types.Bool `tfsdk:"agent"`
AgentSocket types.String `tfsdk:"agent_socket"` AgentSocket types.String `tfsdk:"agent_socket"`
PrivateKey types.String `tfsdk:"private_key"`
Password types.String `tfsdk:"password"` Password types.String `tfsdk:"password"`
Username types.String `tfsdk:"username"` Username types.String `tfsdk:"username"`
Socks5Server types.String `tfsdk:"socks5_server"` Socks5Server types.String `tfsdk:"socks5_server"`
@ -145,8 +146,9 @@ func (p *proxmoxProvider) Schema(_ context.Context, _ provider.SchemaRequest, re
NestedObject: schema.NestedBlockObject{ NestedObject: schema.NestedBlockObject{
Attributes: map[string]schema.Attribute{ Attributes: map[string]schema.Attribute{
"agent": schema.BoolAttribute{ "agent": schema.BoolAttribute{
Description: "Whether to use the SSH agent for authentication. " + Description: "Whether to use the SSH agent for authentication. Takes precedence over " +
"Defaults to `false`.", "the `private_key` and `password` fields. Defaults to the value of the " +
"`PROXMOX_VE_SSH_AGENT` environment variable, or `false` if not set.",
Optional: true, Optional: true,
}, },
"agent_socket": schema.StringAttribute{ "agent_socket": schema.StringAttribute{
@ -155,6 +157,12 @@ func (p *proxmoxProvider) Schema(_ context.Context, _ provider.SchemaRequest, re
"environment variable.", "environment variable.",
Optional: true, Optional: true,
}, },
"private_key": schema.StringAttribute{
Description: "The unencrypted private key (in PEM format) used for the SSH connection. " +
"Defaults to the value of the `PROXMOX_VE_SSH_PRIVATE_KEY` environment variable.",
Optional: true,
Sensitive: true,
},
"password": schema.StringAttribute{ "password": schema.StringAttribute{
Description: "The password used for the SSH connection. " + Description: "The password used for the SSH connection. " +
"Defaults to the value of the `password` field of the " + "Defaults to the value of the `password` field of the " +
@ -332,6 +340,7 @@ func (p *proxmoxProvider) Configure(
sshUsername := utils.GetAnyStringEnv("PROXMOX_VE_SSH_USERNAME") sshUsername := utils.GetAnyStringEnv("PROXMOX_VE_SSH_USERNAME")
sshPassword := utils.GetAnyStringEnv("PROXMOX_VE_SSH_PASSWORD") sshPassword := utils.GetAnyStringEnv("PROXMOX_VE_SSH_PASSWORD")
sshAgent := utils.GetAnyBoolEnv("PROXMOX_VE_SSH_AGENT") sshAgent := utils.GetAnyBoolEnv("PROXMOX_VE_SSH_AGENT")
sshPrivateKey := utils.GetAnyStringEnv("PROXMOX_VE_SSH_PRIVATE_KEY")
sshAgentSocket := utils.GetAnyStringEnv("SSH_AUTH_SOCK", "PROXMOX_VE_SSH_AUTH_SOCK") sshAgentSocket := utils.GetAnyStringEnv("SSH_AUTH_SOCK", "PROXMOX_VE_SSH_AUTH_SOCK")
sshSocks5Server := utils.GetAnyStringEnv("PROXMOX_VE_SSH_SOCKS5_SERVER") sshSocks5Server := utils.GetAnyStringEnv("PROXMOX_VE_SSH_SOCKS5_SERVER")
sshSocks5Username := utils.GetAnyStringEnv("PROXMOX_VE_SSH_SOCKS5_USERNAME") sshSocks5Username := utils.GetAnyStringEnv("PROXMOX_VE_SSH_SOCKS5_USERNAME")
@ -356,6 +365,10 @@ func (p *proxmoxProvider) Configure(
sshAgentSocket = config.SSH[0].AgentSocket.ValueString() sshAgentSocket = config.SSH[0].AgentSocket.ValueString()
} }
if !config.SSH[0].PrivateKey.IsNull() {
sshPrivateKey = config.SSH[0].PrivateKey.ValueString()
}
if !config.SSH[0].Socks5Server.IsNull() { if !config.SSH[0].Socks5Server.IsNull() {
sshSocks5Server = config.SSH[0].Socks5Server.ValueString() sshSocks5Server = config.SSH[0].Socks5Server.ValueString()
} }
@ -390,7 +403,7 @@ func (p *proxmoxProvider) Configure(
} }
sshClient, err := ssh.NewClient( sshClient, err := ssh.NewClient(
sshUsername, sshPassword, sshAgent, sshAgentSocket, sshUsername, sshPassword, sshAgent, sshAgentSocket, sshPrivateKey,
sshSocks5Server, sshSocks5Username, sshSocks5Password, sshSocks5Server, sshSocks5Username, sshSocks5Password,
&apiResolverWithOverrides{ &apiResolverWithOverrides{
ar: apiResolver{c: apiClient}, ar: apiResolver{c: apiClient},

View File

@ -9,6 +9,7 @@ package tests
import ( import (
"context" "context"
"fmt" "fmt"
"math/rand"
"testing" "testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/helper/resource"
@ -21,6 +22,12 @@ const (
accTestContainerCloneName = "proxmox_virtual_environment_container.test_container_clone" accTestContainerCloneName = "proxmox_virtual_environment_container.test_container_clone"
) )
//nolint:gochecknoglobals
var (
accTestContainerID = 100000 + rand.Intn(99999) //nolint:gosec
accCloneContainerID = 200000 + rand.Intn(99999) //nolint:gosec
)
func TestAccResourceContainer(t *testing.T) { func TestAccResourceContainer(t *testing.T) {
accProviders := testAccMuxProviders(context.Background(), t) accProviders := testAccMuxProviders(context.Background(), t)
@ -50,7 +57,7 @@ resource "proxmox_virtual_environment_download_file" "ubuntu_container_template"
} }
resource "proxmox_virtual_environment_container" "test_container" { resource "proxmox_virtual_environment_container" "test_container" {
node_name = "%s" node_name = "%s"
vm_id = 1100 vm_id = %d
template = %t template = %t
disk { disk {
@ -83,7 +90,7 @@ resource "proxmox_virtual_environment_container" "test_container" {
type = "ubuntu" type = "ubuntu"
} }
} }
`, accTestNodeName, isTemplate) `, accTestNodeName, accTestContainerID, isTemplate)
} }
func testAccResourceContainerCreateCheck(t *testing.T) resource.TestCheckFunc { func testAccResourceContainerCreateCheck(t *testing.T) resource.TestCheckFunc {
@ -92,8 +99,9 @@ func testAccResourceContainerCreateCheck(t *testing.T) resource.TestCheckFunc {
return resource.ComposeTestCheckFunc( return resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(accTestContainerName, "description", "my\ndescription\nvalue\n"), resource.TestCheckResourceAttr(accTestContainerName, "description", "my\ndescription\nvalue\n"),
func(*terraform.State) error { func(*terraform.State) error {
err := getNodesClient().Container(1100).WaitForContainerStatus(context.Background(), "running", 10, 1) err := getNodesClient().Container(accTestContainerID).WaitForContainerStatus(context.Background(), "running", 10, 1)
require.NoError(t, err, "container did not start") require.NoError(t, err, "container did not start")
return nil return nil
}, },
) )
@ -105,17 +113,17 @@ resource "proxmox_virtual_environment_container" "test_container_clone" {
depends_on = [proxmox_virtual_environment_container.test_container] depends_on = [proxmox_virtual_environment_container.test_container]
node_name = "%s" node_name = "%s"
vm_id = 1101 vm_id = %d
clone { clone {
vm_id = 1100 vm_id = proxmox_virtual_environment_container.test_container.id
} }
initialization { initialization {
hostname = "test-clone" hostname = "test-clone"
} }
} }
`, accTestNodeName) `, accTestNodeName, accCloneContainerID)
} }
func testAccResourceContainerCreateCloneCheck(t *testing.T) resource.TestCheckFunc { func testAccResourceContainerCreateCloneCheck(t *testing.T) resource.TestCheckFunc {
@ -123,8 +131,9 @@ func testAccResourceContainerCreateCloneCheck(t *testing.T) resource.TestCheckFu
return resource.ComposeTestCheckFunc( return resource.ComposeTestCheckFunc(
func(*terraform.State) error { func(*terraform.State) error {
err := getNodesClient().Container(1101).WaitForContainerStatus(context.Background(), "running", 10, 1) err := getNodesClient().Container(accCloneContainerID).WaitForContainerStatus(context.Background(), "running", 10, 1)
require.NoError(t, err, "container did not start") require.NoError(t, err, "container did not start")
return nil return nil
}, },
) )

View File

@ -127,9 +127,9 @@ func uploadSnippetFile(t *testing.T, file *os.File) {
sshUsername := utils.GetAnyStringEnv("PROXMOX_VE_SSH_USERNAME") sshUsername := utils.GetAnyStringEnv("PROXMOX_VE_SSH_USERNAME")
sshAgentSocket := utils.GetAnyStringEnv("SSH_AUTH_SOCK", "PROXMOX_VE_SSH_AUTH_SOCK", "PM_VE_SSH_AUTH_SOCK") sshAgentSocket := utils.GetAnyStringEnv("SSH_AUTH_SOCK", "PROXMOX_VE_SSH_AUTH_SOCK", "PM_VE_SSH_AUTH_SOCK")
sshPrivateKey := utils.GetAnyStringEnv("PROXMOX_VE_SSH_PRIVATE_KEY")
sshClient, err := ssh.NewClient( sshClient, err := ssh.NewClient(
sshUsername, "", true, sshAgentSocket, sshUsername, "", true, sshAgentSocket, sshPrivateKey,
"", "", "", "", "", "",
&nodeResolver{ &nodeResolver{
node: ssh.ProxmoxNode{ node: ssh.ProxmoxNode{

View File

@ -58,6 +58,7 @@ type client struct {
password string password string
agent bool agent bool
agentSocket string agentSocket string
privateKey string
socks5Server string socks5Server string
socks5Username string socks5Username string
socks5Password string socks5Password string
@ -68,6 +69,7 @@ type client struct {
func NewClient( func NewClient(
username string, password string, username string, password string,
agent bool, agentSocket string, agent bool, agentSocket string,
privateKey string,
socks5Server string, socks5Username string, socks5Password string, socks5Server string, socks5Username string, socks5Password string,
nodeResolver NodeResolver, nodeResolver NodeResolver,
) (Client, error) { ) (Client, error) {
@ -91,6 +93,7 @@ func NewClient(
password: password, password: password,
agent: agent, agent: agent,
agentSocket: agentSocket, agentSocket: agentSocket,
privateKey: privateKey,
socks5Server: socks5Server, socks5Server: socks5Server,
socks5Username: socks5Username, socks5Username: socks5Username,
socks5Password: socks5Password, socks5Password: socks5Password,
@ -309,12 +312,26 @@ func (c *client) openNodeShell(ctx context.Context, node ProxmoxNode) (*ssh.Clie
return sshClient, nil return sshClient, nil
} }
tflog.Error(ctx, "Failed ssh connection through agent, falling back to password authentication", tflog.Error(ctx, "Failed SSH connection through agent",
map[string]interface{}{ map[string]interface{}{
"error": err, "error": err,
}) })
} }
if c.privateKey != "" {
sshClient, err = c.createSSHClientWithPrivateKey(ctx, cb, kh, sshHost)
if err == nil {
return sshClient, nil
}
tflog.Error(ctx, "Failed SSH connection with private key",
map[string]interface{}{
"error": err,
})
}
tflog.Info(ctx, "Falling back to password authentication for SSH connection")
sshClient, err = c.createSSHClient(ctx, cb, kh, sshHost) sshClient, err = c.createSSHClient(ctx, cb, kh, sshHost)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to authenticate user %q over SSH to %q. Please verify that ssh-agent is "+ return nil, fmt.Errorf("unable to authenticate user %q over SSH to %q. Please verify that ssh-agent is "+
@ -374,6 +391,27 @@ func (c *client) createSSHClientAgent(
return c.connect(ctx, sshHost, sshConfig) return c.connect(ctx, sshHost, sshConfig)
} }
func (c *client) createSSHClientWithPrivateKey(
ctx context.Context,
cb ssh.HostKeyCallback,
kh knownhosts.HostKeyCallback,
sshHost string,
) (*ssh.Client, error) {
privateKey, err := ssh.ParsePrivateKey([]byte(c.privateKey))
if err != nil {
return nil, fmt.Errorf("failed to parse private key: %w", err)
}
sshConfig := &ssh.ClientConfig{
User: c.username,
Auth: []ssh.AuthMethod{ssh.PublicKeys(privateKey)},
HostKeyCallback: cb,
HostKeyAlgorithms: kh.HostKeyAlgorithms(sshHost),
}
return c.connect(ctx, sshHost, sshConfig)
}
func (c *client) connect(ctx context.Context, sshHost string, sshConfig *ssh.ClientConfig) (*ssh.Client, error) { func (c *client) connect(ctx context.Context, sshHost string, sshConfig *ssh.ClientConfig) (*ssh.Client, error) {
if c.socks5Server != "" { if c.socks5Server != "" {
sshClient, err := c.socks5SSHClient(sshHost, sshConfig) sshClient, err := c.socks5SSHClient(sshHost, sshConfig)

View File

@ -111,6 +111,7 @@ func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{},
sshPassword := utils.GetAnyStringEnv("PROXMOX_VE_SSH_PASSWORD", "PM_VE_SSH_PASSWORD") sshPassword := utils.GetAnyStringEnv("PROXMOX_VE_SSH_PASSWORD", "PM_VE_SSH_PASSWORD")
sshAgent := utils.GetAnyBoolEnv("PROXMOX_VE_SSH_AGENT", "PM_VE_SSH_AGENT") sshAgent := utils.GetAnyBoolEnv("PROXMOX_VE_SSH_AGENT", "PM_VE_SSH_AGENT")
sshAgentSocket := utils.GetAnyStringEnv("SSH_AUTH_SOCK", "PROXMOX_VE_SSH_AUTH_SOCK", "PM_VE_SSH_AUTH_SOCK") sshAgentSocket := utils.GetAnyStringEnv("SSH_AUTH_SOCK", "PROXMOX_VE_SSH_AUTH_SOCK", "PM_VE_SSH_AUTH_SOCK")
sshPrivateKey := utils.GetAnyStringEnv("PROXMOX_VE_SSH_PRIVATE_KEY")
sshSocks5Server := utils.GetAnyStringEnv("PROXMOX_VE_SSH_SOCKS5_SERVER") sshSocks5Server := utils.GetAnyStringEnv("PROXMOX_VE_SSH_SOCKS5_SERVER")
sshSocks5Username := utils.GetAnyStringEnv("PROXMOX_VE_SSH_SOCKS5_USERNAME") sshSocks5Username := utils.GetAnyStringEnv("PROXMOX_VE_SSH_SOCKS5_USERNAME")
sshSocks5Password := utils.GetAnyStringEnv("PROXMOX_VE_SSH_SOCKS5_PASSWORD") sshSocks5Password := utils.GetAnyStringEnv("PROXMOX_VE_SSH_SOCKS5_PASSWORD")
@ -135,19 +136,23 @@ func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{},
sshConf[mkProviderSSHAgent] = sshAgent sshConf[mkProviderSSHAgent] = sshAgent
} }
if _, ok := sshConf[mkProviderSSHAgentSocket]; !ok { if v, ok := sshConf[mkProviderSSHAgentSocket]; !ok || v.(string) == "" {
sshConf[mkProviderSSHAgentSocket] = sshAgentSocket sshConf[mkProviderSSHAgentSocket] = sshAgentSocket
} }
if _, ok := sshConf[mkProviderSSHSocks5Server]; !ok { if v, ok := sshConf[mkProviderSSHPrivateKey]; !ok || v.(string) == "" {
sshConf[mkProviderSSHPrivateKey] = sshPrivateKey
}
if v, ok := sshConf[mkProviderSSHSocks5Server]; !ok || v.(string) == "" {
sshConf[mkProviderSSHSocks5Server] = sshSocks5Server sshConf[mkProviderSSHSocks5Server] = sshSocks5Server
} }
if _, ok := sshConf[mkProviderSSHSocks5Username]; !ok { if v, ok := sshConf[mkProviderSSHSocks5Username]; !ok || v.(string) == "" {
sshConf[mkProviderSSHSocks5Username] = sshSocks5Username sshConf[mkProviderSSHSocks5Username] = sshSocks5Username
} }
if _, ok := sshConf[mkProviderSSHSocks5Password]; !ok { if v, ok := sshConf[mkProviderSSHSocks5Password]; !ok || v.(string) == "" {
sshConf[mkProviderSSHSocks5Password] = sshSocks5Password sshConf[mkProviderSSHSocks5Password] = sshSocks5Password
} }
@ -168,6 +173,7 @@ func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{},
sshConf[mkProviderSSHPassword].(string), sshConf[mkProviderSSHPassword].(string),
sshConf[mkProviderSSHAgent].(bool), sshConf[mkProviderSSHAgent].(bool),
sshConf[mkProviderSSHAgentSocket].(string), sshConf[mkProviderSSHAgentSocket].(string),
sshConf[mkProviderSSHPrivateKey].(string),
sshConf[mkProviderSSHSocks5Server].(string), sshConf[mkProviderSSHSocks5Server].(string),
sshConf[mkProviderSSHSocks5Username].(string), sshConf[mkProviderSSHSocks5Username].(string),
sshConf[mkProviderSSHSocks5Password].(string), sshConf[mkProviderSSHSocks5Password].(string),

View File

@ -29,6 +29,7 @@ const (
mkProviderSSHPassword = "password" mkProviderSSHPassword = "password"
mkProviderSSHAgent = "agent" mkProviderSSHAgent = "agent"
mkProviderSSHAgentSocket = "agent_socket" mkProviderSSHAgentSocket = "agent_socket"
mkProviderSSHPrivateKey = "private_key"
mkProviderSSHSocks5Server = "socks5_server" mkProviderSSHSocks5Server = "socks5_server"
mkProviderSSHSocks5Username = "socks5_username" mkProviderSSHSocks5Username = "socks5_username"
mkProviderSSHSocks5Password = "socks5_password" mkProviderSSHSocks5Password = "socks5_password"
@ -123,8 +124,9 @@ func createSchema() map[string]*schema.Schema {
mkProviderSSHAgent: { mkProviderSSHAgent: {
Type: schema.TypeBool, Type: schema.TypeBool,
Optional: true, Optional: true,
Description: "Whether to use the SSH agent for authentication. " + Description: "Whether to use the SSH agent for authentication. Takes precedence over " +
"Defaults to `false`.", "the `private_key` and `password` fields. Defaults to the value of the " +
"`PROXMOX_VE_SSH_AGENT` environment variable, or `false` if not set.",
DefaultFunc: func() (interface{}, error) { DefaultFunc: func() (interface{}, error) {
for _, k := range []string{"PROXMOX_VE_SSH_AGENT", "PM_VE_SSH_AGENT"} { for _, k := range []string{"PROXMOX_VE_SSH_AGENT", "PM_VE_SSH_AGENT"} {
v := os.Getenv(k) v := os.Getenv(k)
@ -148,6 +150,13 @@ func createSchema() map[string]*schema.Schema {
), ),
ValidateFunc: validation.StringIsNotEmpty, ValidateFunc: validation.StringIsNotEmpty,
}, },
mkProviderSSHPrivateKey: {
Type: schema.TypeString,
Optional: true,
Sensitive: true,
Description: "The unencrypted private key (in PEM format) used for the SSH connection. " +
"Defaults to the value of the `PROXMOX_VE_SSH_PRIVATE_KEY` environment variable.",
},
mkProviderSSHSocks5Server: { mkProviderSSHSocks5Server: {
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,