mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-08-22 19:38:35 +00:00
feat(provider): add support for pre(external) auth'd session tokens (#1441)
* feat(provider): add support for pre(external) auth'd session tokens 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> * add //nolint to "todo" comments/questions and lll for build to pass; add flags to terraform-plugin-docs Signed-off-by: vanillaSprinkles <vanillaSprinkles@users.noreply.github.com> * address first iteration of comments: remove auth-payload, improve index.md Signed-off-by: vanillaSprinkles <vanillaSprinkles@users.noreply.github.com> * refactor credentials using struct composition, other minor cleanups Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> * fix linter error Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> * fix make docs, add terraform to handle fmt Signed-off-by: vanillaSprinkles <vanillaSprinkles@users.noreply.github.com> --------- Signed-off-by: vanillaSprinkles <vanillaSprinkles@users.noreply.github.com> Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com> Co-authored-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
This commit is contained in:
parent
bf2d2dc396
commit
eb2f36be21
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -88,5 +88,7 @@ jobs:
|
||||
name: test-log
|
||||
path: /tmp/gotest.log
|
||||
|
||||
- uses: hashicorp/setup-terraform@v3
|
||||
|
||||
- name: Check for uncommitted changes in generated docs
|
||||
run: make docs && git diff --exit-code
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -19,6 +19,7 @@ modules-dev/
|
||||
.run/
|
||||
|
||||
*~
|
||||
*#
|
||||
*.backup
|
||||
*.bak
|
||||
*.dll
|
||||
|
@ -15,10 +15,12 @@ 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_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...)
|
||||
@ -35,15 +37,20 @@ 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_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 (example with username and password):
|
||||
|
||||
```hcl
|
||||
provider "proxmox" {
|
||||
endpoint = "https://10.0.0.2:8006/"
|
||||
|
||||
username = "username@realm"
|
||||
password = "a-strong-password"
|
||||
}
|
||||
@ -54,6 +61,7 @@ A better approach is to extract these values into Terraform variables, and refer
|
||||
```hcl
|
||||
provider "proxmox" {
|
||||
endpoint = var.virtual_environment_endpoint
|
||||
|
||||
username = var.virtual_environment_username
|
||||
password = var.virtual_environment_password
|
||||
}
|
||||
@ -75,12 +83,54 @@ provider "proxmox" {
|
||||
|
||||
```sh
|
||||
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 and csrf_prevention_token into the provider using environment variables `PROXMOX_VE_AUTH_TICKET` and `PROXMOX_VE_CSRF_PREVENTION_TOKEN` (or provider's arguments `auth_ticket` and `csrf_prevention_token`).
|
||||
|
||||
An example 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:
|
||||
|
||||
```hcl
|
||||
provider "proxmox" {
|
||||
endpoint = "https://10.0.0.2:8006/"
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
#!/usr/bin/bash
|
||||
|
||||
## assume vars are set: PROXMOX_VE_ENDPOINT, PROXMOX_VE_USERNAME, PROXMOX_VE_PASSWORD
|
||||
## end-goal: automatically set PROXMOX_VE_AUTH_TICKET and PROXMOX_VE_CSRF_PREVENTION_TOKEN
|
||||
|
||||
_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
|
||||
|
||||
## call the auth api endpoint
|
||||
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}" )
|
||||
|
||||
## check if the response payload needs a TFA (totp) passed, call the auth-api endpoint again
|
||||
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
|
||||
|
||||
|
||||
export PROXMOX_VE_AUTH_TICKET="${auth_ticket}"
|
||||
export PROXMOX_VE_CSRF_PREVENTION_TOKEN="${resp_csrf}"
|
||||
|
||||
terraform plan
|
||||
```
|
||||
|
||||
## SSH Connection
|
||||
|
||||
~> Please read if you are using VMs with custom disk images, or uploading snippets.
|
||||
@ -126,6 +176,7 @@ you can use the `private_key` argument in the `ssh` block (or alternatively `PRO
|
||||
The private key mut not be encrypted, and must be in PEM format.
|
||||
|
||||
You can provide the private key from a file:
|
||||
|
||||
```hcl
|
||||
provider "proxmox" {
|
||||
// ...
|
||||
@ -138,6 +189,7 @@ provider "proxmox" {
|
||||
|
||||
Alternatively, although not recommended due to the increased risk of exposing an unprotected key, heredoc syntax can be used to supply the private key as a string.
|
||||
Note that the content of the private key is injected using `<<-` format to ignore indentation:
|
||||
|
||||
```hcl
|
||||
provider "proxmox" {
|
||||
// ...
|
||||
@ -349,10 +401,17 @@ 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_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`.
|
||||
|
@ -10,7 +10,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
|
||||
@ -60,13 +59,16 @@ 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"`
|
||||
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"`
|
||||
@ -100,12 +102,16 @@ func (p *proxmoxProvider) Schema(_ context.Context, _ provider.SchemaRequest, re
|
||||
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'`,
|
||||
),
|
||||
},
|
||||
"auth_ticket": schema.StringAttribute{
|
||||
Description: "The pre-authenticated Ticket for the Proxmox VE API.",
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
"csrf_prevention_token": schema.StringAttribute{
|
||||
Description: "The pre-authenticated CSRF Prevention Token for the Proxmox VE API.",
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
"endpoint": schema.StringAttribute{
|
||||
Description: "The endpoint for the Proxmox VE API.",
|
||||
@ -134,14 +140,14 @@ func (p *proxmoxProvider) Schema(_ context.Context, _ provider.SchemaRequest, re
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
"username": schema.StringAttribute{
|
||||
Description: "The username for the Proxmox VE API.",
|
||||
Optional: true,
|
||||
},
|
||||
"tmp_dir": schema.StringAttribute{
|
||||
Description: "The alternative temporary directory.",
|
||||
Optional: true,
|
||||
},
|
||||
"username": schema.StringAttribute{
|
||||
Description: "The username for the Proxmox VE API.",
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
Blocks: map[string]schema.Block{
|
||||
// have to define it as a list due to backwards compatibility
|
||||
@ -164,12 +170,6 @@ func (p *proxmoxProvider) Schema(_ context.Context, _ provider.SchemaRequest, re
|
||||
"environment variable.",
|
||||
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{
|
||||
Description: "The password used for the SSH connection. " +
|
||||
"Defaults to the value of the `password` field of the " +
|
||||
@ -177,11 +177,17 @@ func (p *proxmoxProvider) Schema(_ context.Context, _ provider.SchemaRequest, re
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
"username": schema.StringAttribute{
|
||||
Description: "The username used for the SSH connection. " +
|
||||
"Defaults to the value of the `username` field of the " +
|
||||
"`provider` block.",
|
||||
"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,
|
||||
},
|
||||
"socks5_password": schema.StringAttribute{
|
||||
Description: "The password for the SOCKS5 proxy server. " +
|
||||
"Defaults to the value of the `PROXMOX_VE_SSH_SOCKS5_PASSWORD` environment variable.",
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
"socks5_server": schema.StringAttribute{
|
||||
Description: "The address:port of the SOCKS5 proxy server. " +
|
||||
@ -193,11 +199,11 @@ func (p *proxmoxProvider) Schema(_ context.Context, _ provider.SchemaRequest, re
|
||||
"Defaults to the value of the `PROXMOX_VE_SSH_SOCKS5_USERNAME` environment variable.",
|
||||
Optional: true,
|
||||
},
|
||||
"socks5_password": schema.StringAttribute{
|
||||
Description: "The password for the SOCKS5 proxy server. " +
|
||||
"Defaults to the value of the `PROXMOX_VE_SSH_SOCKS5_PASSWORD` environment variable.",
|
||||
"username": schema.StringAttribute{
|
||||
Description: "The username used for the SSH connection. " +
|
||||
"Defaults to the value of the `username` field of the " +
|
||||
"`provider` block.",
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
},
|
||||
Blocks: map[string]schema.Block{
|
||||
@ -205,14 +211,14 @@ func (p *proxmoxProvider) Schema(_ context.Context, _ provider.SchemaRequest, re
|
||||
Description: "Overrides for SSH connection configuration for a Proxmox VE node.",
|
||||
NestedObject: schema.NestedBlockObject{
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"name": schema.StringAttribute{
|
||||
Description: "The name of the Proxmox VE node.",
|
||||
Required: true,
|
||||
},
|
||||
"address": schema.StringAttribute{
|
||||
Description: "The address of the Proxmox VE node.",
|
||||
Required: true,
|
||||
},
|
||||
"name": schema.StringAttribute{
|
||||
Description: "The name of the Proxmox VE node.",
|
||||
Required: true,
|
||||
},
|
||||
"port": schema.Int64Attribute{
|
||||
Description: "The port of the Proxmox VE node.",
|
||||
Optional: true,
|
||||
@ -265,17 +271,15 @@ 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")
|
||||
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()
|
||||
}
|
||||
@ -288,6 +292,18 @@ func (p *proxmoxProvider) Configure(
|
||||
minTLS = config.MinTLS.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()
|
||||
}
|
||||
@ -312,7 +328,7 @@ 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)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(
|
||||
"Unable to create Proxmox VE API credentials",
|
||||
@ -401,12 +417,12 @@ func (p *proxmoxProvider) Configure(
|
||||
}
|
||||
}
|
||||
|
||||
if sshUsername == "" {
|
||||
sshUsername = strings.Split(creds.Username, "@")[0]
|
||||
if sshUsername == "" && creds.UserCredentials != nil {
|
||||
sshUsername = strings.Split(creds.UserCredentials.Username, "@")[0]
|
||||
}
|
||||
|
||||
if sshPassword == "" {
|
||||
sshPassword = creds.Password
|
||||
if sshPassword == "" && creds.UserCredentials != nil {
|
||||
sshPassword = creds.UserCredentials.Password
|
||||
}
|
||||
|
||||
sshClient, err := ssh.NewClient(
|
||||
|
@ -144,12 +144,14 @@ func (e *Environment) Client() api.Client {
|
||||
if e.c == nil {
|
||||
e.once.Do(
|
||||
func() {
|
||||
endpoint := utils.GetAnyStringEnv("PROXMOX_VE_ENDPOINT")
|
||||
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")
|
||||
endpoint := utils.GetAnyStringEnv("PROXMOX_VE_ENDPOINT")
|
||||
apiToken := utils.GetAnyStringEnv("PROXMOX_VE_API_TOKEN")
|
||||
|
||||
creds, err := api.NewCredentials(username, password, "", apiToken)
|
||||
creds, err := api.NewCredentials(username, password, "", apiToken, authTicket, csrfPreventionToken)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -115,11 +115,7 @@ type client struct {
|
||||
}
|
||||
|
||||
// NewClient creates and initializes a VirtualEnvironmentClient instance.
|
||||
func NewClient(creds *Credentials, conn *Connection) (Client, error) {
|
||||
if creds == nil {
|
||||
return nil, errors.New("credentials must not be nil")
|
||||
}
|
||||
|
||||
func NewClient(creds Credentials, conn *Connection) (Client, error) {
|
||||
if conn == nil {
|
||||
return nil, errors.New("connection must not be nil")
|
||||
}
|
||||
@ -128,14 +124,19 @@ func NewClient(creds *Credentials, conn *Connection) (Client, error) {
|
||||
|
||||
var err error
|
||||
|
||||
if creds.APIToken != nil {
|
||||
auth, err = NewTokenAuthenticator(*creds.APIToken)
|
||||
} else {
|
||||
auth, err = NewTicketAuthenticator(conn, creds)
|
||||
switch {
|
||||
case creds.TokenCredentials != nil:
|
||||
auth, err = NewTokenAuthenticator(*creds.TokenCredentials)
|
||||
case creds.TicketCredentials != nil:
|
||||
auth, err = NewTicketAuthenticator(*creds.TicketCredentials)
|
||||
case creds.UserCredentials != nil:
|
||||
auth = NewUserAuthenticator(*creds.UserCredentials, conn)
|
||||
default:
|
||||
return nil, errors.New("must provide either user credentials, an API token, or a ticket")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to create API client: %w", err)
|
||||
}
|
||||
|
||||
return &client{
|
||||
|
@ -8,54 +8,95 @@ package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const rootUsername = "root@pam"
|
||||
|
||||
// Credentials is a struct that holds the credentials for the Proxmox Virtual
|
||||
// Environment API.
|
||||
// Credentials contains the credentials for authenticating with the Proxmox VE API.
|
||||
type Credentials struct {
|
||||
UserCredentials *UserCredentials
|
||||
TokenCredentials *TokenCredentials
|
||||
TicketCredentials *TicketCredentials
|
||||
}
|
||||
|
||||
// UserCredentials contains the username, password, and OTP for authenticating with the Proxmox VE API.
|
||||
type UserCredentials struct {
|
||||
Username string
|
||||
Password string
|
||||
OTP *string
|
||||
APIToken *string
|
||||
OTP string
|
||||
}
|
||||
|
||||
// NewCredentials creates a new Credentials struct.
|
||||
func NewCredentials(username, password, otp, apiToken string) (*Credentials, error) {
|
||||
if apiToken != "" {
|
||||
return &Credentials{
|
||||
APIToken: &apiToken,
|
||||
}, nil
|
||||
// TokenCredentials contains the API token for authenticating with the Proxmox VE API.
|
||||
type TokenCredentials struct {
|
||||
APIToken string
|
||||
}
|
||||
|
||||
if password == "" {
|
||||
return nil, errors.New(
|
||||
"you must specify a password for the Proxmox Virtual Environment API",
|
||||
)
|
||||
// TicketCredentials contains the auth ticket and CSRF prevention token for authenticating with the Proxmox VE API.
|
||||
type TicketCredentials struct {
|
||||
AuthTicket string
|
||||
CSRFPreventionToken string
|
||||
}
|
||||
|
||||
if username == "" {
|
||||
return nil, errors.New(
|
||||
"you must specify a username for the Proxmox Virtual Environment API",
|
||||
)
|
||||
// NewCredentials creates a new set of credentials for authenticating with the Proxmox VE API.
|
||||
// The order of precedence is:
|
||||
// 1. API token
|
||||
// 2. Ticket
|
||||
// 3. User credentials.
|
||||
func NewCredentials(username, password, otp, apiToken, authTicket, csrfPreventionToken string) (Credentials, error) {
|
||||
tok, err := newTokenCredentials(apiToken)
|
||||
if err == nil {
|
||||
return Credentials{TokenCredentials: &tok}, nil
|
||||
}
|
||||
|
||||
tic, err := newTicketCredentials(authTicket, csrfPreventionToken)
|
||||
if err == nil {
|
||||
return Credentials{TicketCredentials: &tic}, nil
|
||||
}
|
||||
|
||||
usr, err := newUserCredentials(username, password, otp)
|
||||
if err == nil {
|
||||
return Credentials{UserCredentials: &usr}, nil
|
||||
}
|
||||
|
||||
return Credentials{}, errors.New("must provide either user credentials, an API token, or a ticket")
|
||||
}
|
||||
|
||||
func newUserCredentials(username, password, otp string) (UserCredentials, error) {
|
||||
if username == "" || password == "" {
|
||||
return UserCredentials{}, errors.New("both username and password are required")
|
||||
}
|
||||
|
||||
if !strings.Contains(username, "@") {
|
||||
return nil, errors.New(
|
||||
"make sure the username for the Proxmox Virtual Environment API ends in '@pve or @pam'",
|
||||
)
|
||||
return UserCredentials{}, errors.New("username must end with '@pve' or '@pam'")
|
||||
}
|
||||
|
||||
c := &Credentials{
|
||||
return UserCredentials{
|
||||
Username: username,
|
||||
Password: password,
|
||||
OTP: otp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if otp != "" {
|
||||
c.OTP = &otp
|
||||
func newTokenCredentials(apiToken string) (TokenCredentials, error) {
|
||||
re := regexp.MustCompile(`^\S+@\S+!\S+=([a-zA-Z0-9-]+)$`)
|
||||
if !re.MatchString(apiToken) {
|
||||
return TokenCredentials{}, errors.New("must be a valid API token, e.g. 'USER@REALM!TOKENID=UUID'")
|
||||
}
|
||||
|
||||
return c, nil
|
||||
return TokenCredentials{
|
||||
APIToken: apiToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newTicketCredentials(authTicket, csrfPreventionToken string) (TicketCredentials, error) {
|
||||
if authTicket == "" || csrfPreventionToken == "" {
|
||||
return TicketCredentials{}, errors.New("both authTicket and csrfPreventionToken are required")
|
||||
}
|
||||
|
||||
return TicketCredentials{
|
||||
AuthTicket: authTicket,
|
||||
CSRFPreventionToken: csrfPreventionToken,
|
||||
}, nil
|
||||
}
|
||||
|
@ -7,114 +7,39 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/utils"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ticketAuthenticator struct {
|
||||
conn *Connection
|
||||
authRequest string
|
||||
authData *AuthenticationResponseData
|
||||
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewTicketAuthenticator returns a new ticket authenticator.
|
||||
func NewTicketAuthenticator(conn *Connection, creds *Credentials) (Authenticator, error) {
|
||||
authRequest := fmt.Sprintf(
|
||||
"username=%s&password=%s",
|
||||
url.QueryEscape(creds.Username),
|
||||
url.QueryEscape(creds.Password),
|
||||
)
|
||||
func NewTicketAuthenticator(creds TicketCredentials) (Authenticator, error) {
|
||||
ard := &AuthenticationResponseData{}
|
||||
ard.Ticket = &(creds.AuthTicket)
|
||||
ard.CSRFPreventionToken = &(creds.CSRFPreventionToken)
|
||||
|
||||
// OTP is optional, and probably doesn't make much sense for most provider users.
|
||||
if creds.OTP != nil {
|
||||
authRequest = fmt.Sprintf("%s&otp=%s", authRequest, url.QueryEscape(*creds.OTP))
|
||||
authTicketSplits := strings.Split(creds.AuthTicket, ":")
|
||||
|
||||
if len(authTicketSplits) > 3 {
|
||||
ard.Username = strings.Split(creds.AuthTicket, ":")[1]
|
||||
} else {
|
||||
return nil, errors.New("AuthTicket must include a valid username")
|
||||
}
|
||||
|
||||
if !strings.Contains(ard.Username, "@") {
|
||||
return nil, errors.New("username must end with '@pve' or '@pam'")
|
||||
}
|
||||
|
||||
return &ticketAuthenticator{
|
||||
conn: conn,
|
||||
authRequest: authRequest,
|
||||
authData: ard,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *ticketAuthenticator) authenticate(ctx context.Context) (*AuthenticationResponseData, error) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
if t.authData != nil {
|
||||
return t.authData, nil
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("%s/%s/access/ticket", t.conn.endpoint, basePathJSONAPI),
|
||||
bytes.NewBufferString(t.authRequest),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create authentication request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
tflog.Debug(ctx, "Sending authentication request", map[string]interface{}{
|
||||
"path": req.URL.Path,
|
||||
})
|
||||
|
||||
//nolint:bodyclose
|
||||
res, err := t.conn.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve authentication response: %w", err)
|
||||
}
|
||||
|
||||
defer utils.CloseOrLogError(ctx)(res.Body)
|
||||
|
||||
err = validateResponseCode(res)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to authenticate: %w", err)
|
||||
}
|
||||
|
||||
resBody := AuthenticationResponseBody{}
|
||||
|
||||
err = json.NewDecoder(res.Body).Decode(&resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode authentication response, %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the authentication response")
|
||||
}
|
||||
|
||||
if resBody.Data.CSRFPreventionToken == nil {
|
||||
return nil, errors.New(
|
||||
"the server did not include a CSRF prevention token in the authentication response",
|
||||
)
|
||||
}
|
||||
|
||||
if resBody.Data.Ticket == nil {
|
||||
return nil, errors.New("the server did not include a ticket in the authentication response")
|
||||
}
|
||||
|
||||
if resBody.Data.Username == "" {
|
||||
return nil, errors.New("the server did not include the username in the authentication response")
|
||||
}
|
||||
|
||||
t.authData = resBody.Data
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
func (t *ticketAuthenticator) IsRoot() bool {
|
||||
return t.authData != nil && t.authData.Username == rootUsername
|
||||
}
|
||||
@ -124,21 +49,16 @@ func (t *ticketAuthenticator) IsRootTicket() bool {
|
||||
}
|
||||
|
||||
// AuthenticateRequest adds authentication data to a new request.
|
||||
func (t *ticketAuthenticator) AuthenticateRequest(ctx context.Context, req *http.Request) error {
|
||||
a, err := t.authenticate(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to authenticate: %w", err)
|
||||
}
|
||||
|
||||
func (t *ticketAuthenticator) AuthenticateRequest(_ context.Context, req *http.Request) error {
|
||||
req.AddCookie(&http.Cookie{
|
||||
HttpOnly: true,
|
||||
Name: "PVEAuthCookie",
|
||||
Secure: true,
|
||||
Value: *a.Ticket,
|
||||
Value: *t.authData.Ticket,
|
||||
})
|
||||
|
||||
if req.Method != http.MethodGet {
|
||||
req.Header.Add("CSRFPreventionToken", *a.CSRFPreventionToken)
|
||||
req.Header.Add("CSRFPreventionToken", *t.authData.CSRFPreventionToken)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -19,7 +19,9 @@ type AuthenticationResponseBody struct {
|
||||
type AuthenticationResponseCapabilities struct {
|
||||
Access *types.CustomPrivileges `json:"access,omitempty"`
|
||||
Datacenter *types.CustomPrivileges `json:"dc,omitempty"`
|
||||
Mapping *types.CustomPrivileges `json:"mapping,omitempty"`
|
||||
Nodes *types.CustomPrivileges `json:"nodes,omitempty"`
|
||||
SDN *types.CustomPrivileges `json:"sdn,omitempty"`
|
||||
Storage *types.CustomPrivileges `json:"storage,omitempty"`
|
||||
VMs *types.CustomPrivileges `json:"vms,omitempty"`
|
||||
}
|
||||
|
@ -19,10 +19,10 @@ type tokenAuthenticator struct {
|
||||
|
||||
// NewTokenAuthenticator creates a new authenticator that uses a PVE API Token
|
||||
// for authentication.
|
||||
func NewTokenAuthenticator(token string) (Authenticator, error) {
|
||||
func NewTokenAuthenticator(toc TokenCredentials) (Authenticator, error) {
|
||||
return &tokenAuthenticator{
|
||||
username: strings.Split(token, "!")[0],
|
||||
token: token,
|
||||
username: strings.Split(toc.APIToken, "!")[0],
|
||||
token: toc.APIToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
151
proxmox/api/user_auth.go
Normal file
151
proxmox/api/user_auth.go
Normal file
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
|
||||
"github.com/bpg/terraform-provider-proxmox/utils"
|
||||
)
|
||||
|
||||
type userAuthenticator struct {
|
||||
conn *Connection
|
||||
authRequest string
|
||||
authData *AuthenticationResponseData
|
||||
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewUserAuthenticator creates a new authenticator that uses a username and password for authentication.
|
||||
func NewUserAuthenticator(creds UserCredentials, conn *Connection) Authenticator {
|
||||
authRequest := fmt.Sprintf(
|
||||
"username=%s&password=%s",
|
||||
url.QueryEscape(creds.Username),
|
||||
url.QueryEscape(creds.Password),
|
||||
)
|
||||
|
||||
// OTP is optional, and probably doesn't make much sense for most provider users.
|
||||
// TOTP uses 2x requests; one with payloads `username=` and `password=`,
|
||||
// (this returns a payload including: 'NeedTFA=1')
|
||||
// followed by a 2nd request with payloads:
|
||||
// `username=`, `tfa-challenge=<firsts response ticket>`, `password=totp:######`,
|
||||
// and header: `CSRFPreventionToken: <first response CSRF>`
|
||||
// Ticket generated lasts for ~2hours (to verify)
|
||||
if creds.OTP != "" {
|
||||
authRequest = fmt.Sprintf("%s&otp=%s", authRequest, url.QueryEscape(creds.OTP))
|
||||
}
|
||||
|
||||
return &userAuthenticator{
|
||||
conn: conn,
|
||||
authRequest: authRequest,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *userAuthenticator) authenticate(ctx context.Context) (*AuthenticationResponseData, error) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
if t.authData != nil {
|
||||
return t.authData, nil
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
fmt.Sprintf("%s/%s/access/ticket", t.conn.endpoint, basePathJSONAPI),
|
||||
bytes.NewBufferString(t.authRequest),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create authentication request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
tflog.Debug(ctx, "Sending authentication request", map[string]interface{}{
|
||||
"path": req.URL.Path,
|
||||
})
|
||||
|
||||
//nolint:bodyclose
|
||||
res, err := t.conn.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve authentication response: %w", err)
|
||||
}
|
||||
|
||||
defer utils.CloseOrLogError(ctx)(res.Body)
|
||||
|
||||
err = validateResponseCode(res)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to authenticate: %w", err)
|
||||
}
|
||||
|
||||
resBody := AuthenticationResponseBody{}
|
||||
|
||||
err = json.NewDecoder(res.Body).Decode(&resBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode authentication response, %w", err)
|
||||
}
|
||||
|
||||
if resBody.Data == nil {
|
||||
return nil, errors.New("the server did not include a data object in the authentication response")
|
||||
}
|
||||
|
||||
if resBody.Data.CSRFPreventionToken == nil {
|
||||
return nil, errors.New(
|
||||
"the server did not include a CSRF prevention token in the authentication response",
|
||||
)
|
||||
}
|
||||
|
||||
if resBody.Data.Ticket == nil {
|
||||
return nil, errors.New("the server did not include a ticket in the authentication response")
|
||||
}
|
||||
|
||||
if resBody.Data.Username == "" {
|
||||
return nil, errors.New("the server did not include the username in the authentication response")
|
||||
}
|
||||
|
||||
t.authData = resBody.Data
|
||||
|
||||
return resBody.Data, nil
|
||||
}
|
||||
|
||||
func (t *userAuthenticator) IsRoot() bool {
|
||||
return t.authData != nil && t.authData.Username == rootUsername
|
||||
}
|
||||
|
||||
func (t *userAuthenticator) IsRootTicket() bool {
|
||||
return t.IsRoot()
|
||||
}
|
||||
|
||||
// AuthenticateRequest adds authentication data to a new request.
|
||||
func (t *userAuthenticator) AuthenticateRequest(ctx context.Context, req *http.Request) error {
|
||||
a, err := t.authenticate(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to authenticate: %w", err)
|
||||
}
|
||||
|
||||
req.AddCookie(&http.Cookie{
|
||||
HttpOnly: true,
|
||||
Name: "PVEAuthCookie",
|
||||
Secure: true,
|
||||
Value: *a.Ticket,
|
||||
})
|
||||
|
||||
if req.Method != http.MethodGet {
|
||||
req.Header.Add("CSRFPreventionToken", *a.CSRFPreventionToken)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -42,22 +42,20 @@ func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{},
|
||||
|
||||
var sshClient ssh.Client
|
||||
|
||||
var creds *api.Credentials
|
||||
var creds api.Credentials
|
||||
|
||||
var conn *api.Connection
|
||||
|
||||
// Check environment variables
|
||||
apiToken := utils.GetAnyStringEnv("PROXMOX_VE_API_TOKEN", "PM_VE_API_TOKEN")
|
||||
endpoint := utils.GetAnyStringEnv("PROXMOX_VE_ENDPOINT", "PM_VE_ENDPOINT")
|
||||
insecure := utils.GetAnyBoolEnv("PROXMOX_VE_INSECURE", "PM_VE_INSECURE")
|
||||
minTLS := utils.GetAnyStringEnv("PROXMOX_VE_MIN_TLS", "PM_VE_MIN_TLS")
|
||||
authTicket := utils.GetAnyStringEnv("PROXMOX_VE_AUTH_TICKET", "PM_VE_AUTH_TICKET")
|
||||
csrfPreventionToken := utils.GetAnyStringEnv("PROXMOX_VE_CSRF_PREVENTION_TOKEN", "PM_VE_CSRF_PREVENTION_TOKEN")
|
||||
apiToken := utils.GetAnyStringEnv("PROXMOX_VE_API_TOKEN", "PM_VE_API_TOKEN")
|
||||
otp := utils.GetAnyStringEnv("PROXMOX_VE_OTP", "PM_VE_OTP")
|
||||
username := utils.GetAnyStringEnv("PROXMOX_VE_USERNAME", "PM_VE_USERNAME")
|
||||
password := utils.GetAnyStringEnv("PROXMOX_VE_PASSWORD", "PM_VE_PASSWORD")
|
||||
otp := utils.GetAnyStringEnv("PROXMOX_VE_OTP", "PM_VE_OTP")
|
||||
|
||||
if v, ok := d.GetOk(mkProviderAPIToken); ok {
|
||||
apiToken = v.(string)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk(mkProviderEndpoint); ok {
|
||||
endpoint = v.(string)
|
||||
@ -71,6 +69,22 @@ func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{},
|
||||
minTLS = v.(string)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk(mkProviderAuthTicket); ok {
|
||||
authTicket = v.(string)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk(mkProviderCSRFPreventionToken); ok {
|
||||
csrfPreventionToken = v.(string)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk(mkProviderAPIToken); ok {
|
||||
apiToken = v.(string)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk(mkProviderOTP); ok {
|
||||
otp = v.(string)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk(mkProviderUsername); ok {
|
||||
username = v.(string)
|
||||
}
|
||||
@ -79,11 +93,7 @@ func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{},
|
||||
password = v.(string)
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk(mkProviderOTP); ok {
|
||||
otp = v.(string)
|
||||
}
|
||||
|
||||
creds, err = api.NewCredentials(username, password, otp, apiToken)
|
||||
creds, err = api.NewCredentials(username, password, otp, apiToken, authTicket, csrfPreventionToken)
|
||||
diags = append(diags, diag.FromErr(err)...)
|
||||
|
||||
conn, err = api.NewConnection(endpoint, insecure, minTLS)
|
||||
@ -117,18 +127,24 @@ func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{},
|
||||
sshSocks5Password := utils.GetAnyStringEnv("PROXMOX_VE_SSH_SOCKS5_PASSWORD")
|
||||
|
||||
if v, ok := sshConf[mkProviderSSHUsername]; !ok || v.(string) == "" {
|
||||
if sshUsername != "" {
|
||||
switch {
|
||||
case sshUsername != "":
|
||||
sshConf[mkProviderSSHUsername] = sshUsername
|
||||
} else {
|
||||
sshConf[mkProviderSSHUsername] = strings.Split(creds.Username, "@")[0]
|
||||
case creds.UserCredentials != nil:
|
||||
sshConf[mkProviderSSHUsername] = strings.Split(creds.UserCredentials.Username, "@")[0]
|
||||
default:
|
||||
sshConf[mkProviderSSHUsername] = ""
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := sshConf[mkProviderSSHPassword]; !ok || v.(string) == "" {
|
||||
if sshPassword != "" {
|
||||
switch {
|
||||
case sshPassword != "":
|
||||
sshConf[mkProviderSSHPassword] = sshPassword
|
||||
} else {
|
||||
sshConf[mkProviderSSHPassword] = creds.Password
|
||||
case creds.UserCredentials != nil:
|
||||
sshConf[mkProviderSSHPassword] = creds.UserCredentials.Password
|
||||
default:
|
||||
sshConf[mkProviderSSHPassword] = ""
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,21 +31,25 @@ func TestProviderSchema(t *testing.T) {
|
||||
s := ProxmoxVirtualEnvironment().Schema
|
||||
|
||||
test.AssertOptionalArguments(t, s, []string{
|
||||
mkProviderUsername,
|
||||
mkProviderPassword,
|
||||
mkProviderEndpoint,
|
||||
mkProviderInsecure,
|
||||
mkProviderMinTLS,
|
||||
mkProviderAuthTicket,
|
||||
mkProviderCSRFPreventionToken,
|
||||
mkProviderOTP,
|
||||
mkProviderUsername,
|
||||
mkProviderPassword,
|
||||
})
|
||||
|
||||
test.AssertValueTypes(t, s, map[string]schema.ValueType{
|
||||
mkProviderUsername: schema.TypeString,
|
||||
mkProviderPassword: schema.TypeString,
|
||||
mkProviderEndpoint: schema.TypeString,
|
||||
mkProviderInsecure: schema.TypeBool,
|
||||
mkProviderMinTLS: schema.TypeString,
|
||||
mkProviderAuthTicket: schema.TypeString,
|
||||
mkProviderCSRFPreventionToken: schema.TypeString,
|
||||
mkProviderOTP: schema.TypeString,
|
||||
mkProviderUsername: schema.TypeString,
|
||||
mkProviderPassword: schema.TypeString,
|
||||
})
|
||||
|
||||
providerSSHSchema := test.AssertNestedSchemaExistence(t, s, mkProviderSSH)
|
||||
|
@ -8,7 +8,6 @@ package provider
|
||||
|
||||
import (
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
|
||||
@ -18,10 +17,12 @@ const (
|
||||
mkProviderEndpoint = "endpoint"
|
||||
mkProviderInsecure = "insecure"
|
||||
mkProviderMinTLS = "min_tls"
|
||||
mkProviderAuthTicket = "auth_ticket"
|
||||
mkProviderCSRFPreventionToken = "csrf_prevention_token" // #nosec G101
|
||||
mkProviderAPIToken = "api_token"
|
||||
mkProviderOTP = "otp"
|
||||
mkProviderPassword = "password"
|
||||
mkProviderUsername = "username"
|
||||
mkProviderAPIToken = "api_token"
|
||||
mkProviderTmpDir = "tmp_dir"
|
||||
mkProviderSSH = "ssh"
|
||||
mkProviderSSHUsername = "username"
|
||||
@ -58,24 +59,18 @@ func createSchema() map[string]*schema.Schema {
|
||||
Description: "The minimum required TLS version for API calls." +
|
||||
"Supported values: `1.0|1.1|1.2|1.3`. Defaults to `1.3`.",
|
||||
},
|
||||
mkProviderOTP: {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "The one-time password for the Proxmox VE API.",
|
||||
Deprecated: "The `otp` attribute is deprecated and will be removed in a future release. " +
|
||||
"Please use the `api_token` attribute instead.",
|
||||
},
|
||||
mkProviderPassword: {
|
||||
mkProviderAuthTicket: {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
Description: "The password for the Proxmox VE API.",
|
||||
Description: "The pre-authenticated Ticket for the Proxmox VE API.",
|
||||
ValidateFunc: validation.StringIsNotEmpty,
|
||||
},
|
||||
mkProviderUsername: {
|
||||
mkProviderCSRFPreventionToken: {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "The username for the Proxmox VE API.",
|
||||
Sensitive: true,
|
||||
Description: "The pre-authenticated CSRF Prevention Token for the Proxmox VE API.",
|
||||
ValidateFunc: validation.StringIsNotEmpty,
|
||||
},
|
||||
mkProviderAPIToken: {
|
||||
@ -83,10 +78,27 @@ func createSchema() map[string]*schema.Schema {
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
Description: "The API token for the Proxmox VE API.",
|
||||
ValidateDiagFunc: validation.ToDiagFunc(validation.StringMatch(
|
||||
regexp.MustCompile(`^\S+@\S+!\S+=([a-zA-Z0-9-]+)$`),
|
||||
"Must be a valid API token, e.g. 'USER@REALM!TOKENID=UUID'",
|
||||
)),
|
||||
ValidateFunc: validation.StringIsNotEmpty,
|
||||
},
|
||||
mkProviderOTP: {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "The one-time password for the Proxmox VE API.",
|
||||
Deprecated: "The `otp` attribute is deprecated and will be removed in a future release. " +
|
||||
"Please use the `api_token` attribute instead.",
|
||||
},
|
||||
mkProviderUsername: {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "The username for the Proxmox VE API.",
|
||||
ValidateFunc: validation.StringIsNotEmpty,
|
||||
},
|
||||
mkProviderPassword: {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
Description: "The password for the Proxmox VE API.",
|
||||
ValidateFunc: validation.StringIsNotEmpty,
|
||||
},
|
||||
mkProviderSSH: {
|
||||
Type: schema.TypeList,
|
||||
|
@ -886,7 +886,6 @@ func readURL(
|
||||
if httpLastModified != "" {
|
||||
var timeParsed time.Time
|
||||
timeParsed, err = time.Parse(time.RFC1123, httpLastModified)
|
||||
|
||||
if err != nil {
|
||||
timeParsed, err = time.Parse(time.RFC1123Z, httpLastModified)
|
||||
if err != nil {
|
||||
|
@ -23,7 +23,7 @@ import (
|
||||
// to ensure the documentation is formatted properly.
|
||||
//go:generate terraform fmt -recursive ../examples/
|
||||
// Generate documentation.
|
||||
//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs generate --provider-dir ../ --rendered-website-dir ./build/docs-gen
|
||||
//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs generate --provider-dir ../ --rendered-website-dir ./build/docs-gen --provider-name "terraform-provider-proxmox" --rendered-provider-name "terraform-provider-proxmox" //nolint:lll
|
||||
|
||||
// Temporary: while migrating to the TF framework, we need to copy the generated docs to the right place
|
||||
// for the resources / data sources that have been migrated.
|
||||
|
Loading…
Reference in New Issue
Block a user