Skip to content

Commit

Permalink
feat(provider): add support for pre(external) auth'd session tokens
Browse files Browse the repository at this point in the history
adds provider config inputs:
  - env vars: PROXMOX_VE_AUTH_PAYLOAD; PROXMOX_VE_AUTH_TICKET with PROXMOX_VE_CSRF_PREVENTION_TOKEN
  - provider-config: auth_payload; auth_ticket with csrf_prevention_token

Signed-off-by: vanillaSprinkles <vanillaSprinkles@users.noreply.github.com>
  • Loading branch information
vanillaSprinkles committed Jul 20, 2024
1 parent f116127 commit ea6ef82
Show file tree
Hide file tree
Showing 10 changed files with 379 additions and 107 deletions.
104 changes: 99 additions & 5 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,23 @@ Use the navigation to the left to read about the available resources.
```hcl
provider "proxmox" {
endpoint = "https://10.0.0.2:8006/"
# TODO: use terraform variable or remove the line, and use PROXMOX_VE_AUTH_PAYLOAD environment variable
# auth_payload = '{"data":{"CSRFPreventionToken":"12345678:some_blob","username":"username@realm","ticket":"PVE:username@realmy:12345678::some_base64_payload==","cap":{"access":{"User.Modify":1}}}}'
# TODO: use terraform variable or remove the line, and use PROXMOX_VE_AUTH_TICKET environment variable
# auth_ticket = "PVE:username@realm:12345678::some_base64_payload=="
# TODO: use terraform variable or remove the line, and use PROXMOX_VE_CSRF_PREVENTION_TOKEN= environment variable
# csrf_prevention_token = "12345678:some_blob"
# TODO: use terraform variable or remove the line, and use PROXMOX_VE_API_TOKEN environment variable
# api_token = "root@pam!for-terraform-provider=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# TODO: use terraform variable or remove the line, and use PROXMOX_VE_USERNAME environment variable
username = "root@pam"
# TODO: use terraform variable or remove the line, and use PROXMOX_VE_PASSWORD environment variable
password = "the-password-set-during-installation-of-proxmox-ve"
# because self-signed TLS certificate is in use
insecure = true
# uncomment (unless on Windows...)
Expand All @@ -35,15 +48,28 @@ provider "proxmox" {
## Authentication

The Proxmox provider offers a flexible means of providing credentials for authentication.
Static credentials can be provided to the `proxmox` block through either a `api_token` or a combination of `username` and `password` arguments.
Static credentials and pre-authenticated session-ticket can be provided to the `proxmox` block through one the choices of arguments below, ordered by precedence:
- `auth_payload`
- `auth_ticket` and `csrf_prevention_token`
- `api_token`
- `username` and `password`

!> Hard-coding credentials into any Terraform configuration is not recommended, and risks secret leakage should this file ever be committed to a public version control system.

Static credentials can be provided by adding a `username` and `password`, or `api_token` in-line in the Proxmox provider block:
Static credentials can be provided in-line in the Proxmox provider block, by adding one of the arguments above:

```hcl
provider "proxmox" {
endpoint = "https://10.0.0.2:8006/"
auth_payload = "{\"data\":{\"CSRFPreventionToken\":\"12345678:some_blob\",\"username\":\"username@realm\",\"ticket\":\"PVE:username@realmn:12345678::some_base64_payload==\",\"cap\":{\"access\":{\"User.Modify\":1}}}}"
# setting the auth_payload into terraform will need (at least) its double-quotes escaped; even better to use as an environment-variable
auth_ticket = "PVE:username@realm:12345678::some_base64_payload=="
csrf_prevention_token = "12345678:some_blob"
api_token = "username@realm!for-terraform-provider=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
username = "username@realm"
password = "a-strong-password"
}
Expand All @@ -54,6 +80,14 @@ A better approach is to extract these values into Terraform variables, and refer
```hcl
provider "proxmox" {
endpoint = var.virtual_environment_endpoint
auth_payload = var.virtual_environment_auth_payload
auth_ticket = var.virtual_environment_auth_ticket
csrf_prevention_token = var.virtual_environment_csrf_prevention_token
api_token = var.virtual_environment_api_token
username = var.virtual_environment_username
password = var.virtual_environment_password
}
Expand All @@ -62,6 +96,7 @@ provider "proxmox" {
The variable values can be provided via a separate `.tfvars` file that should be gitignored.
See the [Terraform documentation](https://www.terraform.io/docs/configuration/variables.html) for more information.


### Environment variables

Instead of using static arguments, credentials can be handled through the use of environment variables.
Expand All @@ -74,13 +109,63 @@ provider "proxmox" {
```

```sh
export PROXMOX_VE_AUTH_PAYLOAD='{"data":{"CSRFPreventionToken":"12345678:some_blob","username":"username@realm","ticket":"PVE:username@realm:12345678::some_base64_payload==","cap":{"access":{"User.Modify":1}}}}'

export PROXMOX_VE_AUTH_TICKET='PVE:username@realm:12345678::some_base64_payload=='
export PROXMOX_VE_CSRF_PREVENTION_TOKEN='12345678:some_blob'

export PROXMOX_VE_API_TOKEN='username@realm!for-terraform-provider=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'

export PROXMOX_VE_USERNAME="username@realm"
export PROXMOX_VE_PASSWORD="a-strong-password"
export PROXMOX_VE_PASSWORD='a-strong-password'
terraform plan
```

See the [Argument Reference](#argument-reference) section for the supported variable names and use cases.

## Pre-Authentication, or Passing an Authentication Ticket into the provider

It is possible to generate a session ticket with the api, and to pass the ticket-json into the provider with 1 of 2 methods:
- env-var `PROXMOX_VE_AUTH_PAYLOAD` (or auth_payload)
- env-vars `PROXMOX_VE_AUTH_TICKET` with `PROXMOX_VE_CSRF_PREVENTION_TOKEN` (auth_ticket with csrf_prevention_token)

An example `bash` of using `curl` and `jq` to query the proxmox api to get a proxmox session ticket; it is also very easy to pass in a TOTP password this way
- Note: the export'ing of the 3x vars are redundant and shown for demonstration purposes, pick either: `PROXMOX_VE_AUTH_PAYLOAD` or `PROXMOX_VE_AUTH_TICKET` with `PROXMOX_VE_CSRF_PREVENTION_TOKEN`

```bash
#!/usr/bin/bash

## assume vars are set: PROXMOX_VE_ENDPOINT, PROXMOX_VE_USERNAME, PROXMOX_VE_PASSWORD
## end-goal: automatically set PROXMOX_VE_AUTH_PAYLOAD

_user_totp_password='123456' ## optional TOTP password

proxmox_api_ticket_path='api2/json/access/ticket' ## cannot have double "//" - ensure endpoint ends with a "/" and this string does not begin with a "/", or vice-versa


resp=$( curl -q -s -k --data-urlencode "username=${PROXMOX_VE_USERNAME}" --data-urlencode "password=${PROXMOX_VE_PASSWORD}" "${PROXMOX_VE_ENDPOINT}${proxmox_api_ticket_path}" )
auth_ticket=$( jq -r '.data.ticket' <<<"${resp}" )
resp_csrf=$( jq -r '.data.CSRFPreventionToken' <<<"${resp}" )


if [[ $(jq -r '.data.NeedTFA' <<<"${resp}") == 1 ]]; then
resp=$( curl -q -s -k -H "CSRFPreventionToken: ${resp_csrf}" --data-urlencode "username=${PROXMOX_VE_USERNAME}" --data-urlencode "tfa-challenge=${auth_ticket}" --data-urlencode "password=totp:${_user_totp_password}" "${PROXMOX_VE_ENDPOINT}${proxmox_api_ticket_path}" )
auth_ticket=$( jq -r '.data.ticket' <<<"${resp}" )
resp_csrf=$( jq -r '.data.CSRFPreventionToken' <<<"${resp}" )
fi



## PROXMOX_VE_AUTH_PAYLOAD
export PROXMOX_VE_AUTH_PAYLOAD="${resp}"


## PROXMOX_VE_AUTH_TICKET with PROXMOX_VE_CSRF_PREVENTION_TOKEN
export PROXMOX_VE_AUTH_TICKET="${auth_ticket}"
export PROXMOX_VE_CSRF_PREVENTION_TOKEN="${resp_csrf}"

```

## SSH Connection

~> Please read if you are using VMs with custom disk images, or uploading snippets.
Expand Down Expand Up @@ -346,10 +431,19 @@ In addition to [generic provider arguments](https://www.terraform.io/docs/config
- `endpoint` - (Required) The endpoint for the Proxmox Virtual Environment API (can also be sourced from `PROXMOX_VE_ENDPOINT`). Usually this is `https://<your-cluster-endpoint>:8006/`. **Do not** include `/api2/json` at the end.
- `insecure` - (Optional) Whether to skip the TLS verification step (can also be sourced from `PROXMOX_VE_INSECURE`). If omitted, defaults to `false`.
- `min_tls` - (Optional) The minimum required TLS version for API calls (can also be sourced from `PROXMOX_VE_MIN_TLS`). Supported values: `1.0|1.1|1.2|1.3`. If omitted, defaults to `1.3`.

- `auth_payload` - (Optional) The full authentication ticket json from an external auth call (can also be sourced from `PROXMOX_VE_AUTH_PAYLOAD`). Takes precedence over other api auth-methods. For example, `{"data":{"CSRFPreventionToken":"12345678:some_blob","username":"username@realm","ticket":"PVE:username@realm:12345678::some_base64_payload==","cap":{"access":{"User.Modify":1}}}}`.

- `auth_ticket` - (Optional) The auth ticket from an external auth call (can also be sourced from `PROXMOX_VE_AUTH_TICKET`). To be used in conjunction with `csrf_prevention_token`, takes precedence over `api_token` and `username` with `password`. For example, `PVE:username@realm:12345678::some_base64_payload==`.
- `csrf_prevention_token` - (Optional) The CSRF Prevention Token from an external auth call (can also be sourced from `PROXMOX_VE_CSRF_PREVENTION_TOKEN`). For example, `12345678:some_blob`.

- `api_token` - (Optional) The API Token for the Proxmox Virtual Environment API (can also be sourced from `PROXMOX_VE_API_TOKEN`). Takes precedence over `username` with `password. For example, `username@realm!for-terraform-provider=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`.
- `otp` - (Optional, Deprecated) The one-time password for the Proxmox Virtual Environment API (can also be sourced from `PROXMOX_VE_OTP`).
- `password` - (Required) The password for the Proxmox Virtual Environment API (can also be sourced from `PROXMOX_VE_PASSWORD`).
- `username` - (Required) The username and realm for the Proxmox Virtual Environment API (can also be sourced from `PROXMOX_VE_USERNAME`). For example, `root@pam`.
- `api_token` - (Optional) The API Token for the Proxmox Virtual Environment API (can also be sourced from `PROXMOX_VE_API_TOKEN`). For example, `root@pam!for-terraform-provider=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`.
- `password` - (Required) The password for the Proxmox Virtual Environment API (can also be sourced from `PROXMOX_VE_PASSWORD`).
- `ssh` - (Optional) The SSH connection configuration to a Proxmox node. This is a block, whose fields are documented below.
- `username` - (Optional) The username to use for the SSH connection. Defaults to the username used for the Proxmox API connection. Can also be sourced from `PROXMOX_VE_SSH_USERNAME`. Required when using API Token.
- `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`.
Expand Down
80 changes: 54 additions & 26 deletions fwprovider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"context"
"fmt"
"net"
"regexp"
"strings"

"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
Expand Down Expand Up @@ -59,14 +58,18 @@ type proxmoxProvider struct {

// proxmoxProviderModel maps provider schema data.
type proxmoxProviderModel struct {
APIToken types.String `tfsdk:"api_token"`
Endpoint types.String `tfsdk:"endpoint"`
Insecure types.Bool `tfsdk:"insecure"`
MinTLS types.String `tfsdk:"min_tls"`
OTP types.String `tfsdk:"otp"`
Username types.String `tfsdk:"username"`
Password types.String `tfsdk:"password"`
SSH []struct {
Endpoint types.String `tfsdk:"endpoint"`
Insecure types.Bool `tfsdk:"insecure"`
MinTLS types.String `tfsdk:"min_tls"`
AuthPayload types.String `tfsdk:"auth_payload"`
AuthTicket types.String `tfsdk:"auth_ticket"`
CSRFPreventionToken types.String `tfsdk:"csrf_prevention_token"`
APIToken types.String `tfsdk:"api_token"`
OTP types.String `tfsdk:"otp"`
Username types.String `tfsdk:"username"`
Password types.String `tfsdk:"password"`

SSH []struct {
Agent types.Bool `tfsdk:"agent"`
AgentSocket types.String `tfsdk:"agent_socket"`
PrivateKey types.String `tfsdk:"private_key"`
Expand Down Expand Up @@ -95,17 +98,6 @@ func (p *proxmoxProvider) Schema(_ context.Context, _ provider.SchemaRequest, re
resp.Schema = schema.Schema{
// Attributes specified in alphabetical order.
Attributes: map[string]schema.Attribute{
"api_token": schema.StringAttribute{
Description: "The API token for the Proxmox VE API.",
Optional: true,
Sensitive: true,
Validators: []validator.String{
stringvalidator.RegexMatches(
regexp.MustCompile(`^\S+@\S+!\S+=([a-zA-Z0-9-]+)$`),
`must be a valid API token, e.g. 'USER@REALM!TOKENID=UUID'`,
),
},
},
"endpoint": schema.StringAttribute{
Description: "The endpoint for the Proxmox VE API.",
Optional: true,
Expand All @@ -122,6 +114,26 @@ func (p *proxmoxProvider) Schema(_ context.Context, _ provider.SchemaRequest, re
"Supported values: `1.0|1.1|1.2|1.3`. Defaults to `1.3`.",
Optional: true,
},
"auth_payload": schema.StringAttribute{
Description: "The pre-authd full Ticket Payload json for the Proxmox VE API (takes precedence over auth_ticket).",
Optional: true,
Sensitive: true,
},
"auth_ticket": schema.StringAttribute{
Description: "The pre-authd Ticket for the Proxmox VE API.",
Optional: true,
Sensitive: true,
},
"csrf_prevention_token": schema.StringAttribute{
Description: "The pre-authd CSRF Prevention Token for the Proxmox VE API.",
Optional: true,
Sensitive: true,
},
"api_token": schema.StringAttribute{
Description: "The API token for the Proxmox VE API.",
Optional: true,
Sensitive: true,
},
"otp": schema.StringAttribute{
Description: "The one-time password for the Proxmox VE API.",
Optional: true,
Expand Down Expand Up @@ -264,17 +276,16 @@ func (p *proxmoxProvider) Configure(
// with Terraform configuration value if set.

// Check environment variables
apiToken := utils.GetAnyStringEnv("PROXMOX_VE_API_TOKEN")
endpoint := utils.GetAnyStringEnv("PROXMOX_VE_ENDPOINT")
insecure := utils.GetAnyBoolEnv("PROXMOX_VE_INSECURE")
minTLS := utils.GetAnyStringEnv("PROXMOX_VE_MIN_TLS")
authPayload := utils.GetAnyStringEnv("PROXMOX_VE_AUTH_PAYLOAD")
authTicket := utils.GetAnyStringEnv("PROXMOX_VE_AUTH_TICKET")
csrfPreventionToken := utils.GetAnyStringEnv("PROXMOX_VE_CSRF_PREVENTION_TOKEN")
apiToken := utils.GetAnyStringEnv("PROXMOX_VE_API_TOKEN")
username := utils.GetAnyStringEnv("PROXMOX_VE_USERNAME")
password := utils.GetAnyStringEnv("PROXMOX_VE_PASSWORD")

if !config.APIToken.IsNull() {
apiToken = config.APIToken.ValueString()
}

if !config.Endpoint.IsNull() {
endpoint = config.Endpoint.ValueString()
}
Expand All @@ -287,6 +298,22 @@ func (p *proxmoxProvider) Configure(
minTLS = config.MinTLS.ValueString()
}

if !config.AuthPayload.IsNull() {
authPayload = config.AuthPayload.ValueString()
}

if !config.AuthTicket.IsNull() {
authTicket = config.AuthTicket.ValueString()
}

if !config.CSRFPreventionToken.IsNull() {
csrfPreventionToken = config.CSRFPreventionToken.ValueString()
}

if !config.APIToken.IsNull() {
apiToken = config.APIToken.ValueString()
}

if !config.Username.IsNull() {
username = config.Username.ValueString()
}
Expand All @@ -311,7 +338,8 @@ func (p *proxmoxProvider) Configure(

// Create the Proxmox VE API client

creds, err := api.NewCredentials(username, password, "", apiToken)
creds, err := api.NewCredentials(username, password, "", apiToken, authTicket, csrfPreventionToken, authPayload)

if err != nil {
resp.Diagnostics.AddError(
"Unable to create Proxmox VE API credentials",
Expand Down
9 changes: 6 additions & 3 deletions fwprovider/test/test_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,15 @@ func (e *Environment) Client() api.Client {
if e.c == nil {
e.once.Do(
func() {
username := utils.GetAnyStringEnv("PROXMOX_VE_USERNAME")
password := utils.GetAnyStringEnv("PROXMOX_VE_PASSWORD")
endpoint := utils.GetAnyStringEnv("PROXMOX_VE_ENDPOINT")
authPayload := utils.GetAnyStringEnv("PROXMOX_VE_AUTH_PAYLOAD")
authTicket := utils.GetAnyStringEnv("PROXMOX_VE_AUTH_TICKET")
csrfPreventionToken := utils.GetAnyStringEnv("PROXMOX_VE_CSRF_PREVENTION_TOKEN")
apiToken := utils.GetAnyStringEnv("PROXMOX_VE_API_TOKEN")
username := utils.GetAnyStringEnv("PROXMOX_VE_USERNAME")
password := utils.GetAnyStringEnv("PROXMOX_VE_PASSWORD")

creds, err := api.NewCredentials(username, password, "", apiToken)
creds, err := api.NewCredentials(username, password, "", apiToken, authTicket, csrfPreventionToken, authPayload)
if err != nil {
panic(err)
}
Expand Down
2 changes: 2 additions & 0 deletions proxmox/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ func NewClient(creds *Credentials, conn *Connection) (Client, error) {

var err error

// todo maybe move NewTicketAuthenticator cred-input-logic to here
// aka: creds.AuthTicket and creds.AuthTicket != "" && creds.CSRFPreventionToken to here
if creds.APIToken != nil {
auth, err = NewTokenAuthenticator(*creds.APIToken)
} else {
Expand Down
Loading

0 comments on commit ea6ef82

Please sign in to comment.