diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ee5f437f7..6a27c877c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,13 @@ The v1.12.x release series is supported until **February 1 2027**. UPGRADE NOTES: -- The `OPENTOFU_USER_AGENT` environment variable, which allowed fully overriding the default User-Agent header on all HTTP requests has been removed. +- The "winrm" connection type for the `remote-exec` and `file` provisioners is now deprecated. ([#3899](https://github.com/opentofu/opentofu/issues/3899)) + + The library ecosystem around the WinRM protocol is no longer in a healthy state, with some libraries unmaintained. Therefore we cannot continue to offer this functionality, and will phase it out over the next few release series. In OpenTofu v1.12 this connection type is still supported, but will generate a warning each time it is used. We expect that use of this connection type will begin returning an error in OpenTofu v1.13. + + [Modern Windows versions now support OpenSSH](https://learn.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse), and so we suggest that anyone currently relying on WinRM should begin planning to migrate to using SSH instead. + +- The `OPENTOFU_USER_AGENT` environment variable, which allowed fully overriding the default User-Agent header on all HTTP requests, has been removed. - This is the last OpenTofu release series that will support macOS 12 Monterey. We expect that OpenTofu v1.13 will require macOS 13 Ventura or later. - On Unix systems OpenTofu now considers the `BROWSER` environment variable as a possible override for the default behavior for launching a web browser. diff --git a/internal/tofu/node_resource_validate.go b/internal/tofu/node_resource_validate.go index 11ba2e4f79..11bd0038b7 100644 --- a/internal/tofu/node_resource_validate.go +++ b/internal/tofu/node_resource_validate.go @@ -158,9 +158,42 @@ func (n *NodeValidatableResource) validateProvisioner(ctx context.Context, evalC // configuration keys that are not valid for *any* communicator, catching // typos early rather than waiting until we actually try to run one of // the resource's provisioners. - _, _, connDiags := n.evaluateBlock(ctx, evalCtx, p.Connection.Config, shared.ConnectionBlockSupersetSchema) + connVal, _, connDiags := n.evaluateBlock(ctx, evalCtx, p.Connection.Config, shared.ConnectionBlockSupersetSchema) diags = diags.Append(connDiags) + + if connVal == cty.NilVal || diags.HasErrors() { + return diags + } + + // Now that we have the connection config, we can check if we're using winrm and provide a deprecation warning + // Otherwise, we can just exit early and assume validation is complete + // See https://github.com/opentofu/opentofu/issues/3406 for more information + unmarkedConnVal, _ := connVal.UnmarkDeep() + typeVal := unmarkedConnVal.GetAttr("type") + + if !typeVal.IsKnown() || typeVal.IsNull() || typeVal.AsString() != "winrm" { + return diags + } + + // Set a default range to show in case for some reason we can't get the range for the type attribute + subject := p.Connection.DeclRange.Ptr() + // But we do want to attempt to get the range for the "type" attribute if we can, since that's what we're talking about in the diagnostic. + // Displaying this to end users may reduce some confusion about the location of what needs changing + if attrs, _ := p.Connection.Config.JustAttributes(); attrs != nil { + r := attrs["type"].Expr.Range() + subject = &r + } + + // Now that we have the subject, we can add a diagnostic about the deprecation of the winrm connection type + diags = diags.Append(&hcl.Diagnostic{ + Severity: hcl.DiagWarning, + Summary: "WinRM connection type is deprecated", + Detail: "The winrm connection type is deprecated and will be removed in a future version of OpenTofu.\n\nModern Windows systems support SSH natively. Migrate your provisioners to use SSH instead.\n\nTo get started with OpenSSH on Windows, refer to https://learn.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse", + Subject: subject, + Context: hcl.RangeBetween(p.Connection.DeclRange, p.Connection.Config.MissingItemRange()).Ptr(), + }) } + return diags } diff --git a/internal/tofu/node_resource_validate_test.go b/internal/tofu/node_resource_validate_test.go index 23e03f1d6b..ded8c8e0b3 100644 --- a/internal/tofu/node_resource_validate_test.go +++ b/internal/tofu/node_resource_validate_test.go @@ -116,6 +116,89 @@ func TestNodeValidatableResource_ValidateProvisioner__warning(t *testing.T) { } } +func TestNodeValidatableResource_ValidateProvisioner_winRM_deprecated(t *testing.T) { + ctx := &MockEvalContext{} + ctx.installSimpleEval() + mp := &MockProvisioner{} + mp.GetSchemaResponse = provisioners.GetSchemaResponse{Provisioner: &configschema.Block{}} + ctx.ProvisionersProvisioners = plugins.NewLibrary(nil, map[string]provisioners.Factory{ + "baz": func() (provisioners.Interface, error) { return mp, nil }, + }).NewProvisionerManager() + + stringPtr := func(s string) *string { return &s } + + testCases := []struct { + name string + config *configs.Provisioner + expectedDiagSummary *string + }{ + { + name: "winrm connection should show deprecation warning", + config: &configs.Provisioner{ + Type: "baz", + Config: hcl.EmptyBody(), + Connection: &configs.Connection{ + Config: configs.SynthBody("", map[string]cty.Value{ + "type": cty.StringVal("winrm"), + "host": cty.StringVal("localhost"), + }), + }, + }, + expectedDiagSummary: stringPtr("WinRM connection type is deprecated"), + }, + { + name: "ssh connection should not show deprecation warning", + config: &configs.Provisioner{ + Type: "baz", + Config: hcl.EmptyBody(), + Connection: &configs.Connection{ + Config: configs.SynthBody("", map[string]cty.Value{ + "type": cty.StringVal("ssh"), + "host": cty.StringVal("localhost"), + }), + }, + }, + expectedDiagSummary: nil, + }, + } + + rc := &configs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_foo", + Name: "bar", + Config: configs.SynthBody("", map[string]cty.Value{}), + Managed: &configs.ManagedResource{}, + } + + node := NodeValidatableResource{ + NodeAbstractResource: &NodeAbstractResource{ + Addr: mustConfigResourceAddr("test_foo.bar"), + Config: rc, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + diags := node.validateProvisioner(t.Context(), ctx, tt.config) + if tt.expectedDiagSummary == nil { + if len(diags) != 0 { + t.Fatalf("unexpected warnings: %s", diags.ErrWithWarnings()) + } + } else { + if len(diags) != 1 { + t.Fatalf("wrong number of diagnostics in %s; want one warning, got %d", diags.ErrWithWarnings(), len(diags)) + } + got := diags[0].Description().Summary + want := *tt.expectedDiagSummary + + if got != want { + t.Fatalf("wrong warning %q; want %q", got, want) + } + } + }) + } +} + func TestNodeValidatableResource_ValidateProvisioner__connectionInvalid(t *testing.T) { ctx := &MockEvalContext{} ctx.installSimpleEval() @@ -648,7 +731,7 @@ func TestNodeValidatableResource_ValidateResource_invalidIgnoreChangesComputed(t GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{ Provider: providers.Schema{Block: ms}, ResourceTypes: map[string]providers.Schema{ - "test_object": providers.Schema{Block: ms}, + "test_object": {Block: ms}, }, }, } diff --git a/website/docs/language/resources/provisioners/connection.mdx b/website/docs/language/resources/provisioners/connection.mdx index 380aa97469..8f4f69f6e6 100644 --- a/website/docs/language/resources/provisioners/connection.mdx +++ b/website/docs/language/resources/provisioners/connection.mdx @@ -1,13 +1,13 @@ --- description: >- - The connection block allows you to manage provisioner connection defaults for - SSH and WinRM. + The connection block describes connection settings for the remote-exec and file provisioners. --- # Provisioner Connection Settings -Most provisioners require access to the remote resource via SSH or WinRM and -expect a nested `connection` block with details about how to connect. +The `remote-exec` and `file` provisioners access a remote system using the +Secure Shell (SSH) protocol. These provisioners must therefore be used with +a `connection` block that describes how to connect. :::warning Important Use provisioners as a last resort. There are better alternatives for most situations. Refer to @@ -16,7 +16,7 @@ Use provisioners as a last resort. There are better alternatives for most situat ## Connection Block -You can create one or more `connection` blocks that describe how to access the remote resource. One use case for providing multiple connections is to have an initial provisioner connect as the `root` user to set up user accounts and then have subsequent provisioners connect as a user with more limited permissions. +You can create one or more `connection` blocks that describe how to access the remote system. Connection blocks don't take a block label and can be nested within either a `resource` or a `provisioner`. @@ -26,115 +26,89 @@ Connection blocks don't take a block label and can be nested within either a * A `connection` block nested in a `provisioner` block only affects that provisioner and overrides any resource-level connection settings. -Since the SSH connection type is most often used with -newly-created remote resources, validation of SSH host keys is disabled by -default. If this is not acceptable, you can establish a separate mechanism for key distribution and explicitly set the `host_key` argument (details below) to verify against a specific key or signing CA. +A `connection` block at the resource level is most common, but overriding the +connection settings for just one of a series of provisioners can be useful if, +for example, the first provisioner uses administrative access to create another +user account and then the remaining provisioners use that new user account. + +Remote provisioners are most often used with newly-created servers, and so +validation of host SSH keys is disabled by default. If that is not acceptable +then you can set the `host_key` argument to require the use of a specific key +or of a certificate signed by a specific certificate authority. ### Example usage ```hcl -# Copies the file as the root user using SSH provisioner "file" { source = "conf/myapp.conf" destination = "/etc/myapp.conf" connection { - type = "ssh" user = "root" - password = "${var.root_password}" - host = "${var.host}" - } -} - -# Copies the file as the Administrator user using WinRM -provisioner "file" { - source = "conf/myapp.conf" - destination = "C:/App/myapp.conf" - - connection { - type = "winrm" - user = "Administrator" - password = "${var.admin_password}" - host = "${var.host}" + password = var.root_password + host = var.host } } ``` -### The `self` Object - -Expressions in `connection` blocks cannot refer to their parent resource by name. References create dependencies, and referring to a resource by name within its own block would create a dependency cycle. Instead, expressions can use the `self` object, which represents the connection's parent resource and has all of that resource's attributes. For example, use `self.public_ip` to reference an `aws_instance`'s `public_ip` attribute. - - ### Argument Reference -The `connection` block supports the following arguments. Some arguments are only supported by either the SSH or the WinRM connection type. - - -| Argument | Connection Type | Description | Default | -|---------------|--------------|-------------|---------| -| `type` | Both | The connection type. Valid values are `"ssh"` and `"winrm"`. Provisioners typically assume that the remote system runs Microsoft Windows when using WinRM. Behaviors based on the SSH `target_platform` will force Windows-specific behavior for WinRM, unless otherwise specified.| `"ssh"` | -| `user` | Both | The user to use for the connection. | `root` for type `"ssh"`
`Administrator` for type `"winrm"` | -| `password` | Both | The password to use for the connection. | | -| `host` | Both | **Required** - The address of the resource to connect to. | | -| `port` | Both| The port to connect to. | `22` for type `"ssh"`
`5985` for type `"winrm"` | -| `timeout` | Both | The timeout to wait for the connection to become available. Should be provided as a string (e.g., `"30s"` or `"5m"`.) | `"5m"` | -| `script_path` | Both | The path used to copy scripts meant for remote execution. Refer to [How Provisioners Execute Remote Scripts](#how-provisioners-execute-remote-scripts) below for more details. | (details below) | -| `private_key` | SSH | The contents of an SSH key to use for the connection. These can be loaded from a file on disk using [the `file` function](../../../language/functions/file.mdx). This takes preference over `password` if provided. | | -| `certificate` | SSH | The contents of a signed CA Certificate. The certificate argument must be used in conjunction with a `private_key`. These can be loaded from a file on disk using the [the `file` function](../../../language/functions/file.mdx). | | -| `agent` | SSH | Set to `false` to disable using `ssh-agent` to authenticate. On Windows the only supported SSH authentication agent is [Pageant](http://the.earth.li/\~sgtatham/putty/0.66/htmldoc/Chapter9.html#pageant). | | -| `agent_identity` | SSH | The preferred identity from the ssh agent for authentication. | | -| `host_key` | SSH | The public key from the remote host or the signing CA, used to verify the connection. | | -| `target_platform` | SSH | The target platform to connect to. Valid values are `"windows"` and `"unix"`. If the platform is set to `windows`, the default `script_path` is `c:\windows\temp\terraform_%RAND%.cmd`, assuming [the SSH default shell](https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_server_configuration#configuring-the-default-shell-for-openssh-in-windows) is `cmd.exe`. If the SSH default shell is PowerShell, set `script_path` to `"c:/windows/temp/terraform_%RAND%.ps1"` | `"unix"` | -| `https` | WinRM | Set to `true` to connect using HTTPS instead of HTTP. | | -| `insecure` | WinRM | Set to `true` to skip validating the HTTPS certificate chain. | | -| `use_ntlm` | WinRM | Set to `true` to use NTLM authentication rather than default (basic authentication), removing the requirement for basic authentication to be enabled within the target guest. Refer to [Authentication for Remote Connections](https://docs.microsoft.com/en-us/windows/win32/winrm/authentication-for-remote-connections) in the Windows App Development documentation for more details. | | -| `cacert` | WinRM | The CA certificate to validate against. | | - -### Ephemeral usage - -This type of block can be configured using [ephemeral values](../../ephemerality/index.mdx) such as variables, outputs, and even ephemeral resource attributes. -:::warning -Although the connection block's values are not referenceable in other contexts, the provisioner may produce output logs that contain values referenced in it's configuration. -This output is suppressed when the provisioner's configuration block contains sensitive or ephemeral values, but will *not* perform this check on connection blocks inherited from -a resource. If not correctly configured, sensitive or ephemeral information may be leaked via the logs. -::: - - -## Connecting through a Bastion Host with SSH - -The `ssh` connection also supports the following arguments to connect -indirectly with a [bastion host](https://en.wikipedia.org/wiki/Bastion_host). +The `connection` block supports the following arguments. | Argument | Description | Default | -|---------------|-------------|---------| -| `bastion_host` | Setting this enables the bastion Host connection. The provisioner will connect to `bastion_host` first, and then connect from there to `host`. | | +|----------|-------------|---------| +| `user` | The user to use for the connection. | `root` | +| `password` | The password to use for the connection. | | +| `host` | **Required** - The address of the resource to connect to. | | +| `port` | The port to connect to. | `22` | +| `timeout` | The timeout to wait for the connection to become available. Should be provided as a string (e.g., `"30s"` or `"5m"`.) | `"5m"` | +| `script_path` | The path used to copy scripts meant for remote execution. Refer to [How Provisioners Execute Remote Scripts](#how-provisioners-execute-remote-scripts) below for more details. | (details below) | +| `private_key` | The contents of an SSH key to use for the connection. These can be loaded from a file on disk using [the `file` function](../../../language/functions/file.mdx). This takes preference over `password` if provided. | | +| `certificate` | The contents of a signed CA Certificate. The certificate argument must be used in conjunction with a `private_key`. These can be loaded from a file on disk using the [the `file` function](../../../language/functions/file.mdx). | | +| `agent` | Set to `false` to disable using `ssh-agent` to authenticate. On Windows the only supported SSH authentication agent is [Pageant](http://the.earth.li/\~sgtatham/putty/0.66/htmldoc/Chapter9.html#pageant). | | +| `agent_identity` | The preferred identity from the ssh agent for authentication. | | +| `host_key` | The public key from the remote host or the signing CA, used to verify the connection. | | +| `target_platform` | The target platform to connect to. Valid values are `"windows"` and `"unix"`. If the platform is set to `windows`, the default `script_path` is `c:\windows\temp\terraform_%RAND%.cmd`, assuming [the SSH default shell](https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_server_configuration#configuring-the-default-shell-for-openssh-in-windows) is `cmd.exe`. If the SSH default shell is PowerShell, set `script_path` to `"c:/windows/temp/terraform_%RAND%.ps1"` | `"unix"` | +| `bastion_host` | Setting this forces using the given hostname as a [bastion host](https://en.wikipedia.org/wiki/Bastion_host). The provisioner will connect to `bastion_host` first, and then connect from there to `host`. | | | `bastion_host_key` | The public key from the remote host or the signing CA, used to verify the host connection. | | | `bastion_port` | The port to use connect to the bastion host. | The value of the `port` field.| | `bastion_user`| The user for the connection to the bastion host. | The value of the `user` field. | | `bastion_password` | The password to use for the bastion host. | The value of the `password` field. | | `bastion_private_key` | The contents of an SSH key file to use for the bastion host. These can be loaded from a file on disk using [the `file` function](../../../language/functions/file.mdx). | The value of the `private_key` field. | | `bastion_certificate` | The contents of a signed CA Certificate. The certificate argument must be used in conjunction with a `bastion_private_key`. These can be loaded from a file on disk using the [the `file` function](../../../language/functions/file.mdx). | - -## Connection through a HTTP Proxy with SSH - -The `ssh` connection also supports the following fields to facilitate connections by SSH over HTTP proxy. - -| Argument | Description | Default | -|---------------|-------------|---------| -| `proxy_scheme` | http or https | | | `proxy_host` | Setting this enables the SSH over HTTP connection. This host will be connected to first, and then the `host` or `bastion_host` connection will be made from there. | | +| `proxy_scheme` | http or https | | | `proxy_port` | The port to use connect to the proxy host. | | | `proxy_user_name` | The username to use connect to the private proxy host. This argument should be specified only if authentication is required for the HTTP Proxy server. | | | `proxy_user_password` | The password to use connect to the private proxy host. This argument should be specified only if authentication is required for the HTTP Proxy server. | | + + +Connection settings can be configured using [ephemeral values](../../ephemerality/index.mdx), such as attributes from ephemeral resources, and ephemeral input variables. + +:::warning +Although a connection block's values are not referenceable in other contexts, the provisioner may produce output logs that contain values from it's connection configuration. +::: + +### The `self` Object + +Expressions in `connection` blocks cannot refer to their parent resource by +name: references create dependencies, and referring to a resource by name within +its own block would create a dependency cycle. + +Instead, expressions can use the `self` object, which represents the +resource instance being provisioned and has all of that object's attributes. For +example, use `self.public_ip` to refer to an `aws_instance`'s `public_ip` +attribute. + ## How Provisioners Execute Remote Scripts -Provisioners which execute commands on a remote system via a protocol such as -SSH typically achieve that by uploading a script file to the remote system -and then asking the default shell to execute it. Provisioners use this strategy -because it then allows you to use all of the typical scripting techniques -supported by that shell, including preserving environment variable values -and other context between script statements. +Provisioners which execute commands on a remote system achieve that by +uploading a script file to the remote system and then asking the default shell +to execute it. Provisioners use this strategy because it then allows you to use +all of the typical scripting techniques supported by that shell, including +preserving environment variable values and other context between script +statements. However, this approach does have some consequences which can be relevant in some unusual situations, even though this is just an implementation detail @@ -173,11 +147,9 @@ If your target system is running Windows, we recommend using forward slashes instead of backslashes, despite the typical convention on Windows, because the OpenTofu language uses backslash as the quoted string escape character. -### Executing Scripts using SSH/SCP - -When using the SSH protocol, provisioners upload their script files using -the Secure Copy Protocol (SCP), which requires that the remote system have -the `scp` service program installed to act as the server for that protocol. +Provisioners upload their script files using the Secure Copy Protocol (SCP), +which requires that the remote system have the `scp` service program installed +to act as the server for that protocol. Provisioners will pass the chosen script path (after `%RAND%` expansion) directly to the remote `scp` process, which is responsible for @@ -187,8 +159,36 @@ user by specifying a relative path: ```hcl connection { - type = "ssh" # ... script_path = "tofu_provisioner_%RAND%.sh" } ``` + +### WinRM Connections (Deprecated) + +OpenTofu v1.11 and earlier allowed connecting to Windows systems using +Microsoft's WinRM protocol, as an alternative to SSH. WinRM support is +deprecated in OpenTofu v1.12, and we expect to remove it completely in +OpenTofu v1.13. + +When using WinRM instead of SSH, the `connection` block expects different +arguments: + +| Argument | Description | Default | +|----------|-------------|---------| +| `type` | **Required** when using WinRM. Set this to `"winrm"` to activate the other arguments in this table. | | +| `user` | The user to use for the connection. | `Administrator` | +| `password` | The password to use for the connection. | | +| `host` | **Required** - The address of the resource to connect to. | | +| `port` | The port to connect to. | `5985` | +| `timeout` | The timeout to wait for the connection to become available. Should be provided as a string (e.g., `"30s"` or `"5m"`.) | `"5m"` | +| `script_path` | The path used to copy scripts meant for remote execution. Refer to [How Provisioners Execute Remote Scripts](#how-provisioners-execute-remote-scripts) above for more details. | (details above) | +| `https` | Set to `true` to connect using HTTPS instead of HTTP. | | +| `insecure` | Set to `true` to skip validating the HTTPS certificate chain. | | +| `use_ntlm` | Set to `true` to use NTLM authentication rather than default (basic authentication), removing the requirement for basic authentication to be enabled within the target guest. Refer to [Authentication for Remote Connections](https://docs.microsoft.com/en-us/windows/win32/winrm/authentication-for-remote-connections) in the Windows App Development documentation for more details. | | +| `cacert` | The CA certificate to validate against. | | + +Any `connection` block with `type = "winrm"` will produce a deprecation warning +in OpenTofu v1.12, and will be rejected with an error in OpenTofu v1.13 and +later. We recommend that anyone currently using WinRM begin planning to migrate +to using SSH. diff --git a/website/docs/language/resources/provisioners/file.mdx b/website/docs/language/resources/provisioners/file.mdx index bbe3a6e64c..afe3d80f09 100644 --- a/website/docs/language/resources/provisioners/file.mdx +++ b/website/docs/language/resources/provisioners/file.mdx @@ -8,8 +8,7 @@ description: >- # File Provisioner The `file` provisioner copies files or directories from the machine -running OpenTofu to the newly created resource. The `file` provisioner -supports both `ssh` and `winrm` type [connections](../../../language/resources/provisioners/connection.mdx). +running OpenTofu to the newly created resource. :::warning Important Use provisioners as a last resort. There are better alternatives for most situations. Refer to @@ -70,6 +69,9 @@ The following arguments are supported: system. See [Destination Paths](#destination-paths) below for more information. +This provisioner copies files to a remote system, so you must also include a +[`connection` block](./connection.mdx) to describe how to connect. + ## Destination Paths The path you provide in the `destination` argument will be evaluated by the diff --git a/website/docs/language/resources/provisioners/local-exec.mdx b/website/docs/language/resources/provisioners/local-exec.mdx index c538cfeba3..9c20f7a8e3 100644 --- a/website/docs/language/resources/provisioners/local-exec.mdx +++ b/website/docs/language/resources/provisioners/local-exec.mdx @@ -65,6 +65,10 @@ The following arguments are supported: * `quiet` - (Optional) If set to `true`, OpenTofu will not print the command to be executed to stdout, and will instead print "Suppressed by quiet=true". Note that the output of the command will still be printed in any case. +`local-exec` does not directly interact with any remote systems, so a +[`connection` block](./connection.mdx) is not required and will be silently +ignored if present. + ### Interpreter Examples ```hcl diff --git a/website/docs/language/resources/provisioners/remote-exec.mdx b/website/docs/language/resources/provisioners/remote-exec.mdx index 8b339bb897..906062318a 100644 --- a/website/docs/language/resources/provisioners/remote-exec.mdx +++ b/website/docs/language/resources/provisioners/remote-exec.mdx @@ -11,10 +11,8 @@ description: >- The `remote-exec` provisioner invokes a script on a remote resource after it is created. This can be used to run a configuration management tool, bootstrap -into a cluster, etc. To invoke a local process, see the `local-exec` -[provisioner](../../../language/resources/provisioners/local-exec.mdx) instead. The `remote-exec` -provisioner requires a [connection](../../../language/resources/provisioners/connection.mdx) -and supports both `ssh` and `winrm`. +into a cluster, etc. To run a program locally, use the the `local-exec` +[provisioner](../../../language/resources/provisioners/local-exec.mdx) instead. :::warning Important Use provisioners as a last resort. There are better alternatives for most situations. Refer to @@ -61,6 +59,9 @@ The following arguments are supported: that will be copied to the remote resource and then executed. They are executed in the order they are provided. This cannot be provided with `inline` or `script`. +This provisioner runs scripts on a remote system, so you must also include a +[`connection` block](./connection.mdx) to describe how to connect. + :::note Since `inline` is implemented by concatenating commands into a script, [`on_failure`](../../../language/resources/provisioners/syntax.mdx#failure-behavior) applies only to the final command in the list. In particular, with `on_failure = fail` (the default behaviour) earlier commands will be allowed to fail, and later commands will also execute. If this behaviour is not desired, consider using `"set -o errexit"` as the first command. :::