Skip to content

Commit ef7a652

Browse files
author
Ravi Tandon
committed
Update Windows example in compute to use cloudinit for password change and terraform remote-exec for iscsi volume mount
1 parent 017ef99 commit ef7a652

File tree

8 files changed

+529
-53
lines changed

8 files changed

+529
-53
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Oracle Cloud Infrastructure Terraform Provider Example for Windows VM
2+
3+
This example contains Terraform configuration to provision a virtual machine in Oracle Cloud Infrastructure (OCI) with Microsoft Windows.
4+
5+
## What this example covers
6+
7+
- Deploying networking resources that creates a VCN, Subnet, Route Table, Internet Gateway and a Security List to allow RDP & WinRM traffic for the VM
8+
- [networking.tf](networking.tf)
9+
- Deploying a Windows VM instance with one of [published images on OCI](https://docs.cloud.oracle.com/iaas/images/)
10+
- [windows.tf](windows.tf)
11+
- Using the Windows version for [Cloud-Init](https://cloud-init.io/) - [Cloudbase-Init](https://cloudbase.it/cloudbase-init/) available on the VM to setup and configure Windows
12+
- [cloudinit.ps1](userdata/cloudinit.ps1) - #ps1_sysnative
13+
- Change the initial Windows VM Password
14+
- Configure WinRM for HTTPS connections
15+
- [cloudinit.yml](userdata/cloudinit.yml) - #cloud-config
16+
- Write custom files
17+
- Using Terraform remote-exec execute custom scripts through WinRM
18+
- [winrm.tf](winrm.tf)
19+
- [setup.ps1](userdata/setup.ps1)
20+
- Mount block volumes (iscsi) attached to the VM
21+
22+
## CloudInit
23+
24+
- The latest [Windows Images](https://docs.cloud.oracle.com/iaas/images/windows-server-2012-r2-vm/) released in and after July 2018 for OCI come with Windows version of [Cloud-Init](https://cloud-init.io/), [Cloudbase-Init](https://cloudbase.it/cloudbase-init/) and WinRM enabled by default, refer to the release notes of the [images](https://docs.cloud.oracle.com/iaas/images/) to ensure the version you choose has these enabled for this example to run
25+
- There are multiple ways in which CloudBase-Init can be used by providing a Base64 encoded metadata in [LaunchInstanceDetails](https://docs.cloud.oracle.com/iaas/api/#/en/iaas/20160918/datatypes/LaunchInstanceDetails), this example uses the #cloud-config and #ps1_sysnative variations
26+
- All the plugins in #cloud-config format may not be supported yet, currently script and runcmd are not supported, but some other plugins like write_files are
27+
- Alternatively, to run custom Windows shell commands or Powershell commands, one can alternately use #ps1_sysnative to have them execute as part of initial setup
28+
29+
## WinRM
30+
31+
- While WinRM is enabled on the images, if you plan to use your own images, you need to configure it using following commands.
32+
33+
```powershell
34+
winrm quickconfig
35+
Enable-PSRemoting
36+
37+
winrm set winrm/config/client/auth '@{Basic="true"}'
38+
winrm set winrm/config/service/auth '@{Basic="true"}'
39+
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
40+
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="300"}'
41+
winrm set winrm/config '@{MaxTimeoutms="1800000"}'
42+
43+
netsh advfirewall firewall add rule name="WinRM HTTP" protocol=TCP dir=in profile=any localport=5985 remoteip=any localip=any action=allow
44+
netsh advfirewall firewall add rule name="WinRM HTTPS" protocol=TCP dir=in profile=any localport=5986 remoteip=any localip=any action=allow
45+
46+
net stop winrm
47+
sc.exe config winrm start=auto
48+
net start winrm
49+
```
50+
51+
- Strongly consider the security aspects of allowing unencrypted connections (HTTP). This example shows how to create a self-signed certificate using Cloudbase-Init to configure WinRM for HTTPS communication
52+
- Based on what ports you have configured for RDP and WinRM you want to setup the Security List for your VCN to allow those ports, this example covers these ports:
53+
- 3389 - RDP
54+
- 5985 - WinRM HTTP
55+
- 5986 - WinRM HTTPS
56+
57+
## Terraform
58+
59+
- Terraform uses the Go based winrm(<https://github.com/masterzen/winrm>) library to make remote-exec connections, this library supports BasicAuth by default and can be extended to support additional authentication models
60+
- The remote-exec in Terraform uses SSH by default, but the type can be changed to `winrm` to execute remote commands on Windows
61+
- Terraform provider supports both `template_file` and `template_cloudinit_config` data sources that can be used to set metadata for LaunchInstanceDetails
62+
63+
## Tips and Troubleshooting
64+
65+
- CloudBase-Init is installed at `C:\Program Files\Cloudbase Solutions\Cloudbase-Init` and you can find its logs in the `log` directory under it. Refer [cloudbase-init tutorial](https://cloudbase-init.readthedocs.io/en/latest/tutorial.html) for more information.
66+
- Use #ps1_sysnative for more advanced VM configuration through Cloudbase-init
67+
- The Cloudbase-Init setup may not have completed when the VM is reported to be ready, you can either introduce a wait or have the VM write to a remote location that you can poll before launching remote-exec via Terraform, this examples waits 60 seconds
68+
- The VM instance Cloud-Init metadata that is passed to LaunchInstanceDetails and then read over in VM is just Base64 encoded, you may want to transfer the new password in a more secure way or change it through another remote-exec that can run post Cloudbase-Init. Further, the example also has the passwords stored in the local state file.
69+
- Refer Terraform recommendations for [Sensitive Data](https://www.terraform.io/docs/state/sensitive-data.html)
70+
- The example covers running various Powershell commands, but for a more reliable solution, you may want to add enough retries and error reporting for setup resiliency
71+
- While setting up HTTP over BasicAuth is easy, it is not a recommended way to connecting to these VMs, consider using HTTPS by configuring WinRM HTTPS listener using your own certificate
72+
- If you are facing certificate based errors for WinRM HTTPS connection it is probably due to using the self-signed certificate using New-SelfSignedCertificate that WinRM does not find compatible in newer operating systems
73+
- Ideally you should use CA based certificate for configuring WinRM
74+
- Alternatively, you can use this Ansible published script [ConfigureRemotingForAnsible.ps1](https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1) to generate a legacy self-signed certificate and configure WinRM to use same.
75+
- This entire script can be passed as an additional part in `template_cloudinit_config` to configure WinRM for HTTPS with a self-signed certificate. If you do so, remove the certificate based section from `cloudinit.ps1` in this example
76+
- To get detailed error and test WinRM connectivity from within the VM, you can use the following commands
77+
```powershell
78+
$cred=Get-Credential
79+
test-wsman -Authentication Basic -UseSSL -Credential $cred
80+
```
81+
- To test if WinRM is correctly configured on the VM and is listening, you can use any of the following methods to troubleshoot
82+
83+
```powershell
84+
# To check if winrm is listening
85+
curl --header "Content-Type: application/soap+xml;charset=UTF-8" --header "WSMANIDENTIFY: unauthenticated" --insecure https://<ip-address>:5986/wsman --data '&lt;s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:wsmid="http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd"&gt;&lt;s:Header/&gt;&lt;s:Body&gt;&lt;wsmid:Identify/&gt;&lt;/s:Body&gt;&lt;/s:Envelope&gt;'
86+
87+
# To check winrm with authentication:
88+
curl --header "Content-Type: application/soap+xml;charset=UTF-8" --insecure https://<ip-address>:5986/wsman --basic -u opc:password --data '&lt;s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:wsmid="http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd"&gt;&lt;s:Header/&gt;&lt;s:Body&gt;&lt;wsmid:Identify/&gt;&lt;/s:Body&gt;&lt;/s:Envelope&gt;'
89+
90+
# Using winrm-cli from https://github.com/masterzen/winrm-cli that uses same underlying library that Terraform uses: https://github.com/masterzen/winrm
91+
./winrm -hostname <ip-address> -username "opc" -password "password" -https -insecure "ipconfig /all"
92+
```
93+
94+
- From the VM instance you can run the following commands to get the metadata
95+
96+
```powershell
97+
curl http://169.254.169.254/opc/v1/instance/
98+
curl http://169.254.169.254/opc/v1/instance/metadata/
99+
curl http://169.254.169.254/opc/v1/instance/metadata/<any-key-name>
100+
101+
# To get user_data
102+
curl http://169.254.169.254/opc/v1/instance/metadata/user_data
103+
```
104+
105+
- If you are facing issues with connecting or using WinRM from Terraform through remote-exec, an alternative approach can be to use local-exec with another library like [pywinrm](https://github.com/diyan/pywinrm)
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#############
2+
# Networking
3+
#############
4+
data "oci_identity_availability_domains" "ADs" {
5+
compartment_id = "${var.tenancy_ocid}"
6+
}
7+
8+
resource "oci_core_virtual_network" "ExampleVCN" {
9+
cidr_block = "10.1.0.0/16"
10+
compartment_id = "${var.compartment_ocid}"
11+
display_name = "TFExampleVCN"
12+
dns_label = "tfexamplevcn"
13+
}
14+
15+
resource "oci_core_internet_gateway" "ExampleInternetGateway" {
16+
compartment_id = "${var.compartment_ocid}"
17+
display_name = "TFExampleInternetGateway"
18+
vcn_id = "${oci_core_virtual_network.ExampleVCN.id}"
19+
}
20+
21+
resource "oci_core_route_table" "ExampleRouteTable" {
22+
compartment_id = "${var.compartment_ocid}"
23+
vcn_id = "${oci_core_virtual_network.ExampleVCN.id}"
24+
display_name = "TFExampleRouteTable"
25+
26+
route_rules {
27+
destination = "0.0.0.0/0"
28+
destination_type = "CIDR_BLOCK"
29+
network_entity_id = "${oci_core_internet_gateway.ExampleInternetGateway.id}"
30+
}
31+
}
32+
33+
# https://docs.cloud.oracle.com/iaas/Content/Compute/Tasks/accessinginstance.htm#one
34+
resource "oci_core_security_list" "ExampleSecurityList" {
35+
compartment_id = "${var.compartment_ocid}"
36+
vcn_id = "${oci_core_virtual_network.ExampleVCN.id}"
37+
display_name = "TFExampleSecurityList"
38+
39+
// allow inbound remote desktop traffic
40+
ingress_security_rules {
41+
protocol = "6" // tcp
42+
source = "0.0.0.0/0"
43+
stateless = false
44+
45+
tcp_options {
46+
// These values correspond to the destination port range.
47+
"min" = 3389
48+
"max" = 3389
49+
}
50+
}
51+
52+
// allow inbound winrm traffic
53+
ingress_security_rules {
54+
protocol = "6" // tcp
55+
source = "0.0.0.0/0"
56+
stateless = false
57+
58+
tcp_options {
59+
// These values correspond to the destination port range.
60+
"min" = 5985
61+
"max" = 5986
62+
}
63+
}
64+
65+
// allow all outbound traffic
66+
egress_security_rules {
67+
protocol = "all"
68+
destination = "0.0.0.0/0"
69+
stateless = false
70+
}
71+
}
72+
73+
###########
74+
# Subnet
75+
###########
76+
resource "oci_core_subnet" "ExampleSubnet" {
77+
availability_domain = "${lookup(data.oci_identity_availability_domains.ADs.availability_domains[var.AD - 1],"name")}"
78+
cidr_block = "10.1.20.0/24"
79+
display_name = "TFExampleSubnet"
80+
dns_label = "tfexamplesubnet"
81+
security_list_ids = ["${oci_core_security_list.ExampleSecurityList.id}"]
82+
compartment_id = "${var.compartment_ocid}"
83+
vcn_id = "${oci_core_virtual_network.ExampleVCN.id}"
84+
route_table_id = "${oci_core_route_table.ExampleRouteTable.id}"
85+
dhcp_options_id = "${oci_core_virtual_network.ExampleVCN.default_dhcp_options_id}"
86+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#ps1_sysnative
2+
3+
# Template variables
4+
$user='${instance_user}'
5+
$password='${instance_password}'
6+
$computerName='${instance_name}'
7+
8+
Write-Output "Changing $user password"
9+
net user $user $password
10+
Write-Output "Changed $user password"
11+
12+
Write-Output "Configuring WinRM"
13+
# Allow unencrypted if you wish to use http 5985 endpoint
14+
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
15+
16+
# Create a self-signed certificate to configure WinRM for HTTPS
17+
$cert = New-SelfSignedCertificate -CertStoreLocation 'Cert:\LocalMachine\My' -DnsName $computerName
18+
Write-Output "Self-signed SSL certificate generated with details: $cert"
19+
20+
$valueSet = @{
21+
Hostname = $computerName
22+
CertificateThumbprint = $cert.Thumbprint
23+
}
24+
25+
$selectorSet = @{
26+
Transport = "HTTPS"
27+
Address = "*"
28+
}
29+
30+
# Remove any prior HTTPS listener
31+
$listeners = Get-ChildItem WSMan:\localhost\Listener
32+
If (!($listeners | Where {$_.Keys -like "TRANSPORT=HTTPS"}))
33+
{
34+
Remove-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorSet
35+
}
36+
37+
Write-Output "Enabling HTTPS listener"
38+
New-WSManInstance -ResourceURI 'winrm/config/Listener' -SelectorSet $selectorSet -ValueSet $valueSet
39+
Write-Output "Enabled HTTPS listener"
40+
41+
Write-Output "Configured WinRM"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#cloud-config
2+
write_files:
3+
- path: C:/init.ps1
4+
content: |
5+
echo "add your custom commands and scripts here"
6+
7+
# Depending on the image that is being used runcmd plugin may not be supported, you may use ps1_sysnative config instead
8+
# Windows-Server-2012-R2-Standard-Edition-VM-2018.07.19-0 - cloudbaseinit.plugins.common.userdataplugins.cloudconfig [-] Plugin 'runcmd' is currently not supported
9+
runcmd:
10+
- echo "Hello from Terraform"
11+
- powershell.exe "C:/init.ps1"
12+
13+
# Depending on the image that is being used script plugin may not be supported, you may use ps1_sysnative config instead
14+
# Windows-Server-2012-R2-Standard-Edition-VM-2018.07.19-0 - cloudbaseinit.plugins.common.userdataplugins.cloudconfig [-] Plugin 'script' is currently not supported
15+
script: |
16+
<powershell>
17+
winrm set winrm/config/client/auth '@{Basic="true"}'
18+
winrm set winrm/config/service/auth '@{Basic="true"}'
19+
winrm set winrm/config/service '@{AllowUnencrypted="true"}'
20+
winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="300"}'
21+
winrm set winrm/config '@{MaxTimeoutms="1800000"}'
22+
$file = Join-Path -Path $env:SystemRoot -ChildPath (Get-Date).ToString("MM-dd-yy-hh-mm")
23+
New-Item $file -ItemType file -Value "Hello from Terraform"
24+
</powershell>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
$ipv4='${volume_ipv4}'
2+
$iqn='${volume_iqn}'
3+
4+
echo 'Hello from terraform' > C:\hello_from_terraform_1234567890.txt
5+
6+
Write-Output 'Configuring ISCSI for Block Volumes'
7+
8+
Set-Service -Name msiscsi -StartupType Automatic
9+
Start-Service msiscsi
10+
11+
New-IscsiTargetPortal -TargetPortalAddress $ipv4
12+
Connect-IscsiTarget -NodeAddress $iqn -TargetPortalAddress $ipv4 -IsPersistent $True
13+
14+
Write-Output 'Configured ISCSI for Block Volumes'
15+
16+
Write-Output 'Configuring the new disk for a partition and file system'
17+
# By default the disk is initialized, if it is not, you can add this command before partitioning: Initialize-Disk -PartitionStyle MBR -PassThru
18+
Get-Disk -Number 1 | New-Partition -AssignDriveLetter -UseMaximumSize | Format-Volume -FileSystem NTFS -NewFileSystemLabel "tfdisk" -Confirm:$false
19+
Write-Output 'Configured the new disk'
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
############
2+
# Variables
3+
############
4+
variable "tenancy_ocid" {}
5+
6+
variable "user_ocid" {}
7+
variable "fingerprint" {}
8+
variable "private_key_path" {}
9+
variable "region" {}
10+
11+
variable "compartment_ocid" {}
12+
13+
variable "cloudinit_ps1" {
14+
default = "cloudinit.ps1"
15+
}
16+
17+
variable "cloudinit_config" {
18+
default = "cloudinit.yml"
19+
}
20+
21+
variable "setup_ps1" {
22+
default = "setup.ps1"
23+
}
24+
25+
variable "userdata" {
26+
default = "userdata"
27+
}
28+
29+
variable "size_in_gbs" {
30+
default = "256"
31+
}
32+
33+
variable "instance_name" {
34+
default = "TFWindows"
35+
}
36+
37+
variable "instance_user" {
38+
default = "opc"
39+
}
40+
41+
variable "AD" {
42+
default = "2"
43+
}
44+
45+
variable "instance_shape" {
46+
default = "VM.Standard1.1"
47+
}
48+
49+
variable image_id {
50+
type = "map"
51+
52+
default = {
53+
# Images released in and after July 2018 have cloudbase-init and winrm enabled by default, refer to the release notes - https://docs.cloud.oracle.com/iaas/images/
54+
# Image OCIDs for Windows-Server-2012-R2-Standard-Edition-VM-2018.07.19-0 - https://docs.cloud.oracle.com/iaas/images/image/256a6d7c-4fc0-47c7-a61c-b6bbf25c8aba/
55+
eu-frankfurt-1 = "ocid1.image.oc1.eu-frankfurt-1.aaaaaaaa3nh5j3l3ip62tb2z4gkcrfn23yhwucui5do5abrk4ttvwclxu7ja"
56+
us-ashburn-1 = "ocid1.image.oc1.iad.aaaaaaaauybkm3gymmgenl3e7eqjcjuh324hbocftnxhyn5o2ghpy4st6xza"
57+
uk-london-1 = "ocid1.image.oc1.uk-london-1.aaaaaaaad3q2sx2ngclnggc4nvpop6szposwxnljvuswhimiszwcsltsvi2q"
58+
us-phoenix-1 = "ocid1.image.oc1.phx.aaaaaaaaqcty27fjcgov3a2hl6bimama5l3isv2ejs7utulnpfw5btyyb7gq"
59+
}
60+
}
61+
62+
############
63+
# Provider
64+
############
65+
provider "oci" {
66+
tenancy_ocid = "${var.tenancy_ocid}"
67+
user_ocid = "${var.user_ocid}"
68+
fingerprint = "${var.fingerprint}"
69+
private_key_path = "${var.private_key_path}"
70+
region = "${var.region}"
71+
}

0 commit comments

Comments
 (0)