mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-08-22 19:38: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:
parent
66ec9f4b9b
commit
2c6d3ad01d
27
.github/workflows/test.yml
vendored
27
.github/workflows/test.yml
vendored
@ -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
|
||||||
|
|
||||||
|
@ -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`.
|
||||||
|
@ -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},
|
||||||
|
@ -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
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -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{
|
||||||
|
@ -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)
|
||||||
|
@ -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),
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user