├── .github └── main.workflow ├── .gitignore ├── LICENSE ├── README.md ├── aws-block-allow-all-cidr.sentinel ├── aws-restrict-instance-type-default.sentinel ├── aws-restrict-instance-type-dev.sentinel ├── aws-restrict-instance-type-prod.sentinel ├── azurerm-block-allow-all-cidr.sentinel ├── azurerm-restrict-vm-size.sentinel ├── gcp-block-allow-all-cidr.sentinel ├── gcp-restrict-machine-type.sentinel ├── main.tf ├── passthrough.sentinel ├── require-modules-from-pmr.sentinel ├── test ├── aws-block-allow-all-cidr │ ├── empty.json │ ├── fail.json │ └── pass.json ├── aws-restrict-instance-type-default │ ├── dev-not-prod.json │ ├── fail.json │ ├── pass.json │ └── prod-not-dev.json ├── aws-restrict-instance-type-dev │ ├── fail.json │ ├── pass.json │ └── prod-not-dev.json ├── aws-restrict-instance-type-prod │ ├── dev-not-prod.json │ ├── fail.json │ └── pass.json ├── azurerm-block-allow-all-cidr │ ├── fail.json │ └── pass.json ├── azurerm-restrict-vm-size │ ├── fail.json │ └── pass.json ├── gcp-block-allow-all-cidr │ ├── fail.json │ └── pass.json ├── gcp-restrict-machine-type │ ├── fail.json │ └── pass.json ├── passthrough │ └── pass.json ├── require-modules-from-pmr │ ├── fail.json │ ├── pass.json │ ├── tfconfig-fail.sentinel │ └── tfconfig-pass.sentinel └── tfe_policies_only │ ├── fail.json │ └── pass.json └── tfe_policies_only.sentinel /.github/main.workflow: -------------------------------------------------------------------------------- 1 | workflow "Sentinel" { 2 | resolves = ["sentinel-test", "terraform-fmt"] 3 | on = "pull_request" 4 | } 5 | 6 | action "sentinel-test" { 7 | uses = "hashicorp/sentinel-github-actions/test@master" 8 | secrets = ["GITHUB_TOKEN"] 9 | env = { 10 | STL_ACTION_WORKING_DIR = "." 11 | } 12 | } 13 | 14 | 15 | action "terraform-fmt" { 16 | uses = "hashicorp/terraform-github-actions/fmt@v0.1" 17 | secrets = ["GITHUB_TOKEN"] 18 | env = { 19 | TF_ACTION_WORKING_DIR = "." 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # .tfvars files 9 | *.tfvars 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 HashiCorp, Inc. 2 | 3 | Mozilla Public License, version 2.0 4 | 5 | 1. Definitions 6 | 7 | 1.1. “Contributor” 8 | 9 | means each individual or legal entity that creates, contributes to the 10 | creation of, or owns Covered Software. 11 | 12 | 1.2. “Contributor Version” 13 | 14 | means the combination of the Contributions of others (if any) used by a 15 | Contributor and that particular Contributor’s Contribution. 16 | 17 | 1.3. “Contribution” 18 | 19 | means Covered Software of a particular Contributor. 20 | 21 | 1.4. “Covered Software” 22 | 23 | means Source Code Form to which the initial Contributor has attached the 24 | notice in Exhibit A, the Executable Form of such Source Code Form, and 25 | Modifications of such Source Code Form, in each case including portions 26 | thereof. 27 | 28 | 1.5. “Incompatible With Secondary Licenses” 29 | means 30 | 31 | a. that the initial Contributor has attached the notice described in 32 | Exhibit B to the Covered Software; or 33 | 34 | b. that the Covered Software was made available under the terms of version 35 | 1.1 or earlier of the License, but not also under the terms of a 36 | Secondary License. 37 | 38 | 1.6. “Executable Form” 39 | 40 | means any form of the work other than Source Code Form. 41 | 42 | 1.7. “Larger Work” 43 | 44 | means a work that combines Covered Software with other material, in a separate 45 | file or files, that is not Covered Software. 46 | 47 | 1.8. “License” 48 | 49 | means this document. 50 | 51 | 1.9. “Licensable” 52 | 53 | means having the right to grant, to the maximum extent possible, whether at the 54 | time of the initial grant or subsequently, any and all of the rights conveyed by 55 | this License. 56 | 57 | 1.10. “Modifications” 58 | 59 | means any of the following: 60 | 61 | a. any file in Source Code Form that results from an addition to, deletion 62 | from, or modification of the contents of Covered Software; or 63 | 64 | b. any new file in Source Code Form that contains any Covered Software. 65 | 66 | 1.11. “Patent Claims” of a Contributor 67 | 68 | means any patent claim(s), including without limitation, method, process, 69 | and apparatus claims, in any patent Licensable by such Contributor that 70 | would be infringed, but for the grant of the License, by the making, 71 | using, selling, offering for sale, having made, import, or transfer of 72 | either its Contributions or its Contributor Version. 73 | 74 | 1.12. “Secondary License” 75 | 76 | means either the GNU General Public License, Version 2.0, the GNU Lesser 77 | General Public License, Version 2.1, the GNU Affero General Public 78 | License, Version 3.0, or any later versions of those licenses. 79 | 80 | 1.13. “Source Code Form” 81 | 82 | means the form of the work preferred for making modifications. 83 | 84 | 1.14. “You” (or “Your”) 85 | 86 | means an individual or a legal entity exercising rights under this 87 | License. For legal entities, “You” includes any entity that controls, is 88 | controlled by, or is under common control with You. For purposes of this 89 | definition, “control” means (a) the power, direct or indirect, to cause 90 | the direction or management of such entity, whether by contract or 91 | otherwise, or (b) ownership of more than fifty percent (50%) of the 92 | outstanding shares or beneficial ownership of such entity. 93 | 94 | 95 | 2. License Grants and Conditions 96 | 97 | 2.1. Grants 98 | 99 | Each Contributor hereby grants You a world-wide, royalty-free, 100 | non-exclusive license: 101 | 102 | a. under intellectual property rights (other than patent or trademark) 103 | Licensable by such Contributor to use, reproduce, make available, 104 | modify, display, perform, distribute, and otherwise exploit its 105 | Contributions, either on an unmodified basis, with Modifications, or as 106 | part of a Larger Work; and 107 | 108 | b. under Patent Claims of such Contributor to make, use, sell, offer for 109 | sale, have made, import, and otherwise transfer either its Contributions 110 | or its Contributor Version. 111 | 112 | 2.2. Effective Date 113 | 114 | The licenses granted in Section 2.1 with respect to any Contribution become 115 | effective for each Contribution on the date the Contributor first distributes 116 | such Contribution. 117 | 118 | 2.3. Limitations on Grant Scope 119 | 120 | The licenses granted in this Section 2 are the only rights granted under this 121 | License. No additional rights or licenses will be implied from the distribution 122 | or licensing of Covered Software under this License. Notwithstanding Section 123 | 2.1(b) above, no patent license is granted by a Contributor: 124 | 125 | a. for any code that a Contributor has removed from Covered Software; or 126 | 127 | b. for infringements caused by: (i) Your and any other third party’s 128 | modifications of Covered Software, or (ii) the combination of its 129 | Contributions with other software (except as part of its Contributor 130 | Version); or 131 | 132 | c. under Patent Claims infringed by Covered Software in the absence of its 133 | Contributions. 134 | 135 | This License does not grant any rights in the trademarks, service marks, or 136 | logos of any Contributor (except as may be necessary to comply with the 137 | notice requirements in Section 3.4). 138 | 139 | 2.4. Subsequent Licenses 140 | 141 | No Contributor makes additional grants as a result of Your choice to 142 | distribute the Covered Software under a subsequent version of this License 143 | (see Section 10.2) or under the terms of a Secondary License (if permitted 144 | under the terms of Section 3.3). 145 | 146 | 2.5. Representation 147 | 148 | Each Contributor represents that the Contributor believes its Contributions 149 | are its original creation(s) or it has sufficient rights to grant the 150 | rights to its Contributions conveyed by this License. 151 | 152 | 2.6. Fair Use 153 | 154 | This License is not intended to limit any rights You have under applicable 155 | copyright doctrines of fair use, fair dealing, or other equivalents. 156 | 157 | 2.7. Conditions 158 | 159 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in 160 | Section 2.1. 161 | 162 | 163 | 3. Responsibilities 164 | 165 | 3.1. Distribution of Source Form 166 | 167 | All distribution of Covered Software in Source Code Form, including any 168 | Modifications that You create or to which You contribute, must be under the 169 | terms of this License. You must inform recipients that the Source Code Form 170 | of the Covered Software is governed by the terms of this License, and how 171 | they can obtain a copy of this License. You may not attempt to alter or 172 | restrict the recipients’ rights in the Source Code Form. 173 | 174 | 3.2. Distribution of Executable Form 175 | 176 | If You distribute Covered Software in Executable Form then: 177 | 178 | a. such Covered Software must also be made available in Source Code Form, 179 | as described in Section 3.1, and You must inform recipients of the 180 | Executable Form how they can obtain a copy of such Source Code Form by 181 | reasonable means in a timely manner, at a charge no more than the cost 182 | of distribution to the recipient; and 183 | 184 | b. You may distribute such Executable Form under the terms of this License, 185 | or sublicense it under different terms, provided that the license for 186 | the Executable Form does not attempt to limit or alter the recipients’ 187 | rights in the Source Code Form under this License. 188 | 189 | 3.3. Distribution of a Larger Work 190 | 191 | You may create and distribute a Larger Work under terms of Your choice, 192 | provided that You also comply with the requirements of this License for the 193 | Covered Software. If the Larger Work is a combination of Covered Software 194 | with a work governed by one or more Secondary Licenses, and the Covered 195 | Software is not Incompatible With Secondary Licenses, this License permits 196 | You to additionally distribute such Covered Software under the terms of 197 | such Secondary License(s), so that the recipient of the Larger Work may, at 198 | their option, further distribute the Covered Software under the terms of 199 | either this License or such Secondary License(s). 200 | 201 | 3.4. Notices 202 | 203 | You may not remove or alter the substance of any license notices (including 204 | copyright notices, patent notices, disclaimers of warranty, or limitations 205 | of liability) contained within the Source Code Form of the Covered 206 | Software, except that You may alter any license notices to the extent 207 | required to remedy known factual inaccuracies. 208 | 209 | 3.5. Application of Additional Terms 210 | 211 | You may choose to offer, and to charge a fee for, warranty, support, 212 | indemnity or liability obligations to one or more recipients of Covered 213 | Software. However, You may do so only on Your own behalf, and not on behalf 214 | of any Contributor. You must make it absolutely clear that any such 215 | warranty, support, indemnity, or liability obligation is offered by You 216 | alone, and You hereby agree to indemnify every Contributor for any 217 | liability incurred by such Contributor as a result of warranty, support, 218 | indemnity or liability terms You offer. You may include additional 219 | disclaimers of warranty and limitations of liability specific to any 220 | jurisdiction. 221 | 222 | 4. Inability to Comply Due to Statute or Regulation 223 | 224 | If it is impossible for You to comply with any of the terms of this License 225 | with respect to some or all of the Covered Software due to statute, judicial 226 | order, or regulation then You must: (a) comply with the terms of this License 227 | to the maximum extent possible; and (b) describe the limitations and the code 228 | they affect. Such description must be placed in a text file included with all 229 | distributions of the Covered Software under this License. Except to the 230 | extent prohibited by statute or regulation, such description must be 231 | sufficiently detailed for a recipient of ordinary skill to be able to 232 | understand it. 233 | 234 | 5. Termination 235 | 236 | 5.1. The rights granted under this License will terminate automatically if You 237 | fail to comply with any of its terms. However, if You become compliant, 238 | then the rights granted under this License from a particular Contributor 239 | are reinstated (a) provisionally, unless and until such Contributor 240 | explicitly and finally terminates Your grants, and (b) on an ongoing basis, 241 | if such Contributor fails to notify You of the non-compliance by some 242 | reasonable means prior to 60 days after You have come back into compliance. 243 | Moreover, Your grants from a particular Contributor are reinstated on an 244 | ongoing basis if such Contributor notifies You of the non-compliance by 245 | some reasonable means, this is the first time You have received notice of 246 | non-compliance with this License from such Contributor, and You become 247 | compliant prior to 30 days after Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, counter-claims, 251 | and cross-claims) alleging that a Contributor Version directly or 252 | indirectly infringes any patent, then the rights granted to You by any and 253 | all Contributors for the Covered Software under Section 2.1 of this License 254 | shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user 257 | license agreements (excluding distributors and resellers) which have been 258 | validly granted by You or Your distributors under this License prior to 259 | termination shall survive termination. 260 | 261 | 6. Disclaimer of Warranty 262 | 263 | Covered Software is provided under this License on an “as is” basis, without 264 | warranty of any kind, either expressed, implied, or statutory, including, 265 | without limitation, warranties that the Covered Software is free of defects, 266 | merchantable, fit for a particular purpose or non-infringing. The entire 267 | risk as to the quality and performance of the Covered Software is with You. 268 | Should any Covered Software prove defective in any respect, You (not any 269 | Contributor) assume the cost of any necessary servicing, repair, or 270 | correction. This disclaimer of warranty constitutes an essential part of this 271 | License. No use of any Covered Software is authorized under this License 272 | except under this disclaimer. 273 | 274 | 7. Limitation of Liability 275 | 276 | Under no circumstances and under no legal theory, whether tort (including 277 | negligence), contract, or otherwise, shall any Contributor, or anyone who 278 | distributes Covered Software as permitted above, be liable to You for any 279 | direct, indirect, special, incidental, or consequential damages of any 280 | character including, without limitation, damages for lost profits, loss of 281 | goodwill, work stoppage, computer failure or malfunction, or any and all 282 | other commercial damages or losses, even if such party shall have been 283 | informed of the possibility of such damages. This limitation of liability 284 | shall not apply to liability for death or personal injury resulting from such 285 | party’s negligence to the extent applicable law prohibits such limitation. 286 | Some jurisdictions do not allow the exclusion or limitation of incidental or 287 | consequential damages, so this exclusion and limitation may not apply to You. 288 | 289 | 8. Litigation 290 | 291 | Any litigation relating to this License may be brought only in the courts of 292 | a jurisdiction where the defendant maintains its principal place of business 293 | and such litigation shall be governed by laws of that jurisdiction, without 294 | reference to its conflict-of-law provisions. Nothing in this Section shall 295 | prevent a party’s ability to bring cross-claims or counter-claims. 296 | 297 | 9. Miscellaneous 298 | 299 | This License represents the complete agreement concerning the subject matter 300 | hereof. If any provision of this License is held to be unenforceable, such 301 | provision shall be reformed only to the extent necessary to make it 302 | enforceable. Any law or regulation which provides that the language of a 303 | contract shall be construed against the drafter shall not be used to construe 304 | this License against a Contributor. 305 | 306 | 307 | 10. Versions of the License 308 | 309 | 10.1. New Versions 310 | 311 | Mozilla Foundation is the license steward. Except as provided in Section 312 | 10.3, no one other than the license steward has the right to modify or 313 | publish new versions of this License. Each version will be given a 314 | distinguishing version number. 315 | 316 | 10.2. Effect of New Versions 317 | 318 | You may distribute the Covered Software under the terms of the version of 319 | the License under which You originally received the Covered Software, or 320 | under the terms of any subsequent version published by the license 321 | steward. 322 | 323 | 10.3. Modified Versions 324 | 325 | If you create software not governed by this License, and you want to 326 | create a new license for such software, you may create and use a modified 327 | version of this License if you rename the license and remove any 328 | references to the name of the license steward (except to note that such 329 | modified license differs from this License). 330 | 331 | 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses 332 | If You choose to distribute Source Code Form that is Incompatible With 333 | Secondary Licenses under the terms of this version of the License, the 334 | notice described in Exhibit B of this License must be attached. 335 | 336 | Exhibit A - Source Code Form License Notice 337 | 338 | This Source Code Form is subject to the 339 | terms of the Mozilla Public License, v. 340 | 2.0. If a copy of the MPL was not 341 | distributed with this file, You can 342 | obtain one at 343 | http://mozilla.org/MPL/2.0/. 344 | 345 | If it is not possible or desirable to put the notice in a particular file, then 346 | You may include the notice in a location (such as a LICENSE file in a relevant 347 | directory) where a recipient would be likely to look for such a notice. 348 | 349 | You may add additional accurate notices of copyright ownership. 350 | 351 | Exhibit B - “Incompatible With Secondary Licenses” Notice 352 | 353 | This Source Code Form is “Incompatible 354 | With Secondary Licenses”, as defined by 355 | the Mozilla Public License, v. 2.0. 356 | 357 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TFE Policies Example 2 | 3 | This repo demonstrates a complete VCS-backed Sentinel workflow for Terraform Enterprise (TFE). It includes the following components: 4 | 5 | - Some example Sentinel policies that define rules about Terraform runs. 6 | - Sentinel test configurations for those policies. 7 | - A Terraform configuration to sync those policies with Terraform Enterprise, group them into sets, and enforce them on workspaces. 8 | 9 | It is intended to be combined with the following: 10 | 11 | - A Terraform Enterprise workspace, which runs Terraform to update your Sentinel policies whenever the repo changes. 12 | - A lightweight CI solution (like GitHub Actions), for continuously testing your Sentinel code. 13 | 14 | > **See also:** This repo shows an end-to-end workflow with many parts, and uses a small number of Sentinel policies to keep things simple. If you'd rather see a wider range of how to govern specific kinds of infrastructure with Sentinel policies, see the [example policies in the hashicorp/terraform-guides repo](https://github.com/hashicorp/terraform-guides/tree/master/governance). 15 | 16 | ## Using with TFE 17 | 18 | Fork this repo, then create a Terraform Enterprise workspace linked to your fork. Set values for the following Terraform variables: 19 | 20 | - `tfe_hostname` (optional; defaults to `app.terraform.io`) — the hostname of your TFE instance. 21 | - `tfe_organization` — the name of your TFE organization. 22 | - `tfe_token` (SENSITIVE) — the organization token or owners team token for your organization. 23 | 24 | Add and remove Sentinel policies as desired, and edit `main.tf` to ensure your policies are enforced on the correct workspaces. Queue an initial run to set up your policies, then continue to iterate on the policy repo and approve Terraform runs as needed. 25 | 26 | For more details, see [Managing Sentinel Policies with Version Control](https://www.terraform.io/docs/enterprise/sentinel/integrate-vcs.html). 27 | 28 | ## Testing Sentinel Policies Locally 29 | 30 | Run all tests: 31 | 32 | > sentinel test 33 | 34 | Manually apply a policy using a specific test config: 35 | 36 | > sentinel apply -config ./test/aws-restrict-instance-type-prod/dev-not-prod.json aws-restrict-instance-type-prod.sentinel 37 | 38 | (This example results in a policy failure, as intended; see the `"test"` property of any test config for the expected behavior.) 39 | 40 | 41 | ## Testing Sentinel Policies with Github Actions 42 | 43 | This repo contains [an example](.github/main.workflow) of running `sentinel test` against your sentinel files as PR checks. It uses a third-party action called `thrashr888/sentinel-github-actions/test` to run the tests. After submitting a PR, you'll see any test errors show up as a comment on the PR. 44 | 45 | -------------------------------------------------------------------------------- /aws-block-allow-all-cidr.sentinel: -------------------------------------------------------------------------------- 1 | import "tfplan" 2 | 3 | # Get an array of all resources of the given type (or an empty array). 4 | get_resources = func(type) { 5 | if length(tfplan.module_paths else []) > 0 { # always true in the real tfplan import 6 | return get_resources_all_modules(type) 7 | } else { # fallback for tests 8 | return get_resources_root_only(type) 9 | } 10 | } 11 | 12 | get_resources_root_only = func(type) { 13 | resources = [] 14 | named_and_counted_resources = tfplan.resources[type] else {} 15 | # Get resource bodies out of nested resource maps, from: 16 | # {"name": {"0": {"applied": {...}, "diff": {...} }, "1": {...}}, "name": {...}} 17 | # to: 18 | # [{"applied": {...}, "diff": {...}}, {"applied": {...}, "diff": {...}}, ...] 19 | for named_and_counted_resources as _, instances { 20 | for instances as _, body { 21 | append(resources, body) 22 | } 23 | } 24 | return resources 25 | } 26 | 27 | get_resources_all_modules = func(type) { 28 | resources = [] 29 | for tfplan.module_paths as path { 30 | named_and_counted_resources = tfplan.module(path).resources[type] else {} 31 | # Get resource bodies out of nested resource maps, from: 32 | # {"name": {"0": {"applied": {...}, "diff": {...} }, "1": {...}}, "name": {...}} 33 | # to: 34 | # [{"applied": {...}, "diff": {...}}, {"applied": {...}, "diff": {...}}, ...] 35 | for named_and_counted_resources as _, instances { 36 | for instances as _, body { 37 | append(resources, body) 38 | } 39 | } 40 | } 41 | return resources 42 | } 43 | 44 | disallowed_cidr_blocks = [ 45 | "0.0.0.0/0", 46 | ] 47 | 48 | main = rule { 49 | all get_resources("aws_security_group") as sg { 50 | all sg.applied.ingress as ingress { 51 | all disallowed_cidr_blocks as block { 52 | ingress.cidr_blocks not contains block 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /aws-restrict-instance-type-default.sentinel: -------------------------------------------------------------------------------- 1 | import "tfplan" 2 | 3 | # Get an array of all resources of the given type (or an empty array). 4 | get_resources = func(type) { 5 | if length(tfplan.module_paths else []) > 0 { # always true in the real tfplan import 6 | return get_resources_all_modules(type) 7 | } else { # fallback for tests 8 | return get_resources_root_only(type) 9 | } 10 | } 11 | 12 | get_resources_root_only = func(type) { 13 | resources = [] 14 | named_and_counted_resources = tfplan.resources[type] else {} 15 | # Get resource bodies out of nested resource maps, from: 16 | # {"name": {"0": {"applied": {...}, "diff": {...} }, "1": {...}}, "name": {...}} 17 | # to: 18 | # [{"applied": {...}, "diff": {...}}, {"applied": {...}, "diff": {...}}, ...] 19 | for named_and_counted_resources as _, instances { 20 | for instances as _, body { 21 | append(resources, body) 22 | } 23 | } 24 | return resources 25 | } 26 | 27 | get_resources_all_modules = func(type) { 28 | resources = [] 29 | for tfplan.module_paths as path { 30 | named_and_counted_resources = tfplan.module(path).resources[type] else {} 31 | # Get resource bodies out of nested resource maps, from: 32 | # {"name": {"0": {"applied": {...}, "diff": {...} }, "1": {...}}, "name": {...}} 33 | # to: 34 | # [{"applied": {...}, "diff": {...}}, {"applied": {...}, "diff": {...}}, ...] 35 | for named_and_counted_resources as _, instances { 36 | for instances as _, body { 37 | append(resources, body) 38 | } 39 | } 40 | } 41 | return resources 42 | } 43 | 44 | # Allowed Types 45 | allowed_types = [ 46 | "t2.nano", 47 | "t2.micro", 48 | "t2.small", 49 | "t2.medium", 50 | "t2.large", 51 | "t2.xlarge", 52 | "m4.large", 53 | "m4.xlarge", 54 | ] 55 | 56 | # Rule to restrict instance types 57 | instance_type_allowed = rule { 58 | all get_resources("aws_instance") as r { 59 | r.applied.instance_type in allowed_types 60 | } 61 | } 62 | 63 | # Main rule that requires other rules to be true 64 | main = rule { 65 | (instance_type_allowed) else true 66 | } -------------------------------------------------------------------------------- /aws-restrict-instance-type-dev.sentinel: -------------------------------------------------------------------------------- 1 | import "tfplan" 2 | 3 | # Get an array of all resources of the given type (or an empty array). 4 | get_resources = func(type) { 5 | if length(tfplan.module_paths else []) > 0 { # always true in the real tfplan import 6 | return get_resources_all_modules(type) 7 | } else { # fallback for tests 8 | return get_resources_root_only(type) 9 | } 10 | } 11 | 12 | get_resources_root_only = func(type) { 13 | resources = [] 14 | named_and_counted_resources = tfplan.resources[type] else {} 15 | # Get resource bodies out of nested resource maps, from: 16 | # {"name": {"0": {"applied": {...}, "diff": {...} }, "1": {...}}, "name": {...}} 17 | # to: 18 | # [{"applied": {...}, "diff": {...}}, {"applied": {...}, "diff": {...}}, ...] 19 | for named_and_counted_resources as _, instances { 20 | for instances as _, body { 21 | append(resources, body) 22 | } 23 | } 24 | return resources 25 | } 26 | 27 | get_resources_all_modules = func(type) { 28 | resources = [] 29 | for tfplan.module_paths as path { 30 | named_and_counted_resources = tfplan.module(path).resources[type] else {} 31 | # Get resource bodies out of nested resource maps, from: 32 | # {"name": {"0": {"applied": {...}, "diff": {...} }, "1": {...}}, "name": {...}} 33 | # to: 34 | # [{"applied": {...}, "diff": {...}}, {"applied": {...}, "diff": {...}}, ...] 35 | for named_and_counted_resources as _, instances { 36 | for instances as _, body { 37 | append(resources, body) 38 | } 39 | } 40 | } 41 | return resources 42 | } 43 | 44 | # Allowed Types 45 | allowed_types = [ 46 | "t2.nano", 47 | "t2.micro", 48 | "t2.small", 49 | "t2.medium", 50 | ] 51 | 52 | # Rule to restrict instance types 53 | instance_type_allowed = rule { 54 | all get_resources("aws_instance") as r { 55 | r.applied.instance_type in allowed_types 56 | } 57 | } 58 | 59 | # Main rule that requires other rules to be true 60 | main = rule { 61 | (instance_type_allowed) else true 62 | } -------------------------------------------------------------------------------- /aws-restrict-instance-type-prod.sentinel: -------------------------------------------------------------------------------- 1 | import "tfplan" 2 | 3 | # Get an array of all resources of the given type (or an empty array). 4 | get_resources = func(type) { 5 | if length(tfplan.module_paths else []) > 0 { # always true in the real tfplan import 6 | return get_resources_all_modules(type) 7 | } else { # fallback for tests 8 | return get_resources_root_only(type) 9 | } 10 | } 11 | 12 | get_resources_root_only = func(type) { 13 | resources = [] 14 | named_and_counted_resources = tfplan.resources[type] else {} 15 | # Get resource bodies out of nested resource maps, from: 16 | # {"name": {"0": {"applied": {...}, "diff": {...} }, "1": {...}}, "name": {...}} 17 | # to: 18 | # [{"applied": {...}, "diff": {...}}, {"applied": {...}, "diff": {...}}, ...] 19 | for named_and_counted_resources as _, instances { 20 | for instances as _, body { 21 | append(resources, body) 22 | } 23 | } 24 | return resources 25 | } 26 | 27 | get_resources_all_modules = func(type) { 28 | resources = [] 29 | for tfplan.module_paths as path { 30 | named_and_counted_resources = tfplan.module(path).resources[type] else {} 31 | # Get resource bodies out of nested resource maps, from: 32 | # {"name": {"0": {"applied": {...}, "diff": {...} }, "1": {...}}, "name": {...}} 33 | # to: 34 | # [{"applied": {...}, "diff": {...}}, {"applied": {...}, "diff": {...}}, ...] 35 | for named_and_counted_resources as _, instances { 36 | for instances as _, body { 37 | append(resources, body) 38 | } 39 | } 40 | } 41 | return resources 42 | } 43 | 44 | # Allowed Types 45 | allowed_types = [ 46 | "t2.small", 47 | "t2.medium", 48 | "t2.large", 49 | "t2.xlarge", 50 | "m4.large", 51 | "m4.xlarge", 52 | ] 53 | 54 | # Rule to restrict instance types 55 | instance_type_allowed = rule { 56 | all get_resources("aws_instance") as r { 57 | r.applied.instance_type in allowed_types 58 | } 59 | } 60 | 61 | # Main rule that requires other rules to be true 62 | main = rule { 63 | (instance_type_allowed) else true 64 | } -------------------------------------------------------------------------------- /azurerm-block-allow-all-cidr.sentinel: -------------------------------------------------------------------------------- 1 | import "tfplan" 2 | 3 | # Get an array of all resources of the given type (or an empty array). 4 | get_resources = func(type) { 5 | if length(tfplan.module_paths else []) > 0 { # always true in the real tfplan import 6 | return get_resources_all_modules(type) 7 | } else { # fallback for tests 8 | return get_resources_root_only(type) 9 | } 10 | } 11 | 12 | get_resources_root_only = func(type) { 13 | resources = [] 14 | named_and_counted_resources = tfplan.resources[type] else {} 15 | # Get resource bodies out of nested resource maps, from: 16 | # {"name": {"0": {"applied": {...}, "diff": {...} }, "1": {...}}, "name": {...}} 17 | # to: 18 | # [{"applied": {...}, "diff": {...}}, {"applied": {...}, "diff": {...}}, ...] 19 | for named_and_counted_resources as _, instances { 20 | for instances as _, body { 21 | append(resources, body) 22 | } 23 | } 24 | return resources 25 | } 26 | 27 | get_resources_all_modules = func(type) { 28 | resources = [] 29 | for tfplan.module_paths as path { 30 | named_and_counted_resources = tfplan.module(path).resources[type] else {} 31 | # Get resource bodies out of nested resource maps, from: 32 | # {"name": {"0": {"applied": {...}, "diff": {...} }, "1": {...}}, "name": {...}} 33 | # to: 34 | # [{"applied": {...}, "diff": {...}}, {"applied": {...}, "diff": {...}}, ...] 35 | for named_and_counted_resources as _, instances { 36 | for instances as _, body { 37 | append(resources, body) 38 | } 39 | } 40 | } 41 | return resources 42 | } 43 | 44 | disallowed_cidr_blocks = [ 45 | "0.0.0.0/0", 46 | "*", 47 | ] 48 | 49 | block_allow_all = rule { 50 | all get_resources("azurerm_network_security_group") as sg { 51 | all sg.applied.security_rule as _, sr { 52 | (sr.source_address_prefix not in disallowed_cidr_blocks) or sr.access == "Deny" 53 | } 54 | } 55 | } 56 | 57 | main = rule { 58 | (block_allow_all) else true 59 | } -------------------------------------------------------------------------------- /azurerm-restrict-vm-size.sentinel: -------------------------------------------------------------------------------- 1 | import "tfplan" 2 | 3 | # Get an array of all resources of the given type (or an empty array). 4 | get_resources = func(type) { 5 | if length(tfplan.module_paths else []) > 0 { # always true in the real tfplan import 6 | return get_resources_all_modules(type) 7 | } else { # fallback for tests 8 | return get_resources_root_only(type) 9 | } 10 | } 11 | 12 | get_resources_root_only = func(type) { 13 | resources = [] 14 | named_and_counted_resources = tfplan.resources[type] else {} 15 | # Get resource bodies out of nested resource maps, from: 16 | # {"name": {"0": {"applied": {...}, "diff": {...} }, "1": {...}}, "name": {...}} 17 | # to: 18 | # [{"applied": {...}, "diff": {...}}, {"applied": {...}, "diff": {...}}, ...] 19 | for named_and_counted_resources as _, instances { 20 | for instances as _, body { 21 | append(resources, body) 22 | } 23 | } 24 | return resources 25 | } 26 | 27 | get_resources_all_modules = func(type) { 28 | resources = [] 29 | for tfplan.module_paths as path { 30 | named_and_counted_resources = tfplan.module(path).resources[type] else {} 31 | # Get resource bodies out of nested resource maps, from: 32 | # {"name": {"0": {"applied": {...}, "diff": {...} }, "1": {...}}, "name": {...}} 33 | # to: 34 | # [{"applied": {...}, "diff": {...}}, {"applied": {...}, "diff": {...}}, ...] 35 | for named_and_counted_resources as _, instances { 36 | for instances as _, body { 37 | append(resources, body) 38 | } 39 | } 40 | } 41 | return resources 42 | } 43 | 44 | # comparison is case-sensitive 45 | # so including both cases for "v" 46 | # since we have seen both used 47 | allowed_vm_sizes = [ 48 | "Standard_D1_v2", 49 | "Standard_D1_V2", 50 | "Standard_D2_v2", 51 | "Standard_D2_V2", 52 | "Standard_DS1_v2", 53 | "Standard_DS1_V2", 54 | "Standard_DS2_v2", 55 | "Standard_DS2_V2", 56 | "Standard_A1", 57 | "Standard_A2", 58 | "Standard_D1", 59 | "Standard_D2", 60 | ] 61 | 62 | vm_size_allowed = rule { 63 | all get_resources("azurerm_virtual_machine") as r { 64 | r.applied.vm_size in allowed_vm_sizes 65 | } 66 | } 67 | 68 | main = rule { 69 | (vm_size_allowed) else true 70 | } -------------------------------------------------------------------------------- /gcp-block-allow-all-cidr.sentinel: -------------------------------------------------------------------------------- 1 | import "tfplan" 2 | 3 | # Get an array of all resources of the given type (or an empty array). 4 | get_resources = func(type) { 5 | if length(tfplan.module_paths else []) > 0 { # always true in the real tfplan import 6 | return get_resources_all_modules(type) 7 | } else { # fallback for tests 8 | return get_resources_root_only(type) 9 | } 10 | } 11 | 12 | get_resources_root_only = func(type) { 13 | resources = [] 14 | named_and_counted_resources = tfplan.resources[type] else {} 15 | # Get resource bodies out of nested resource maps, from: 16 | # {"name": {"0": {"applied": {...}, "diff": {...} }, "1": {...}}, "name": {...}} 17 | # to: 18 | # [{"applied": {...}, "diff": {...}}, {"applied": {...}, "diff": {...}}, ...] 19 | for named_and_counted_resources as _, instances { 20 | for instances as _, body { 21 | append(resources, body) 22 | } 23 | } 24 | return resources 25 | } 26 | 27 | get_resources_all_modules = func(type) { 28 | resources = [] 29 | for tfplan.module_paths as path { 30 | named_and_counted_resources = tfplan.module(path).resources[type] else {} 31 | # Get resource bodies out of nested resource maps, from: 32 | # {"name": {"0": {"applied": {...}, "diff": {...} }, "1": {...}}, "name": {...}} 33 | # to: 34 | # [{"applied": {...}, "diff": {...}}, {"applied": {...}, "diff": {...}}, ...] 35 | for named_and_counted_resources as _, instances { 36 | for instances as _, body { 37 | append(resources, body) 38 | } 39 | } 40 | } 41 | return resources 42 | } 43 | 44 | disallowed_cidr_block = "0.0.0.0/0" 45 | 46 | block_allow_all = rule { 47 | all get_resources("google_compute_firewall") as fw { 48 | disallowed_cidr_block not in fw.applied.source_ranges[0] 49 | } 50 | } 51 | 52 | main = rule { 53 | (block_allow_all) else true 54 | } -------------------------------------------------------------------------------- /gcp-restrict-machine-type.sentinel: -------------------------------------------------------------------------------- 1 | import "tfplan" 2 | 3 | # Get an array of all resources of the given type (or an empty array). 4 | get_resources = func(type) { 5 | if length(tfplan.module_paths else []) > 0 { # always true in the real tfplan import 6 | return get_resources_all_modules(type) 7 | } else { # fallback for tests 8 | return get_resources_root_only(type) 9 | } 10 | } 11 | 12 | get_resources_root_only = func(type) { 13 | resources = [] 14 | named_and_counted_resources = tfplan.resources[type] else {} 15 | # Get resource bodies out of nested resource maps, from: 16 | # {"name": {"0": {"applied": {...}, "diff": {...} }, "1": {...}}, "name": {...}} 17 | # to: 18 | # [{"applied": {...}, "diff": {...}}, {"applied": {...}, "diff": {...}}, ...] 19 | for named_and_counted_resources as _, instances { 20 | for instances as _, body { 21 | append(resources, body) 22 | } 23 | } 24 | return resources 25 | } 26 | 27 | get_resources_all_modules = func(type) { 28 | resources = [] 29 | for tfplan.module_paths as path { 30 | named_and_counted_resources = tfplan.module(path).resources[type] else {} 31 | # Get resource bodies out of nested resource maps, from: 32 | # {"name": {"0": {"applied": {...}, "diff": {...} }, "1": {...}}, "name": {...}} 33 | # to: 34 | # [{"applied": {...}, "diff": {...}}, {"applied": {...}, "diff": {...}}, ...] 35 | for named_and_counted_resources as _, instances { 36 | for instances as _, body { 37 | append(resources, body) 38 | } 39 | } 40 | } 41 | return resources 42 | } 43 | 44 | allowed_machine_types = [ 45 | "n1-standard-1", 46 | "n1-standard-2", 47 | "n1-standard-4", 48 | ] 49 | 50 | machine_type_allowed = rule { 51 | all get_resources("google_compute_instance") as r { 52 | r.applied.machine_type in allowed_machine_types 53 | } 54 | } 55 | 56 | main = rule { 57 | (machine_type_allowed) else true 58 | } -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | backend "remote" { 3 | hostname = "app.terraform.io" 4 | organization = "hashicorp-v2" 5 | 6 | workspaces { 7 | name = "tfe-policies-example" 8 | } 9 | } 10 | } 11 | 12 | variable "tfe_token" {} 13 | 14 | variable "tfe_hostname" { 15 | description = "The domain where your TFE is hosted." 16 | default = "app.terraform.io" 17 | } 18 | 19 | variable "tfe_organization" { 20 | description = "The TFE organization to apply your changes to." 21 | default = "example_corp" 22 | } 23 | 24 | provider "tfe" { 25 | hostname = "${var.tfe_hostname}" 26 | token = "${var.tfe_token}" 27 | version = "~> 0.6" 28 | } 29 | 30 | data "tfe_workspace_ids" "all" { 31 | names = ["*"] 32 | organization = "${var.tfe_organization}" 33 | } 34 | 35 | locals { 36 | workspaces = "${data.tfe_workspace_ids.all.external_ids}" # map of names to IDs 37 | } 38 | 39 | resource "tfe_policy_set" "global" { 40 | name = "global" 41 | description = "Policies that should be enforced on ALL infrastructure." 42 | organization = "${var.tfe_organization}" 43 | global = true 44 | 45 | policy_ids = [ 46 | "${tfe_sentinel_policy.passthrough.id}", 47 | "${tfe_sentinel_policy.aws-block-allow-all-cidr.id}", 48 | "${tfe_sentinel_policy.azurerm-block-allow-all-cidr.id}", 49 | "${tfe_sentinel_policy.gcp-block-allow-all-cidr.id}", 50 | "${tfe_sentinel_policy.aws-restrict-instance-type-default.id}", 51 | "${tfe_sentinel_policy.azurerm-restrict-vm-size.id}", 52 | "${tfe_sentinel_policy.gcp-restrict-machine-type.id}", 53 | "${tfe_sentinel_policy.require-modules-from-pmr.id}", 54 | ] 55 | } 56 | 57 | resource "tfe_policy_set" "production" { 58 | name = "production" 59 | description = "Policies that should be enforced on production infrastructure." 60 | organization = "${var.tfe_organization}" 61 | 62 | policy_ids = [ 63 | "${tfe_sentinel_policy.aws-restrict-instance-type-prod.id}", 64 | ] 65 | 66 | workspace_external_ids = [ 67 | "${local.workspaces["app-prod"]}", 68 | ] 69 | } 70 | 71 | resource "tfe_policy_set" "development" { 72 | name = "development" 73 | description = "Policies that should be enforced on development or scratch infrastructure." 74 | organization = "${var.tfe_organization}" 75 | 76 | policy_ids = [ 77 | "${tfe_sentinel_policy.aws-restrict-instance-type-dev.id}", 78 | ] 79 | 80 | workspace_external_ids = [ 81 | "${local.workspaces["app-dev"]}", 82 | "${local.workspaces["app-dev-sandbox-bennett"]}", 83 | ] 84 | } 85 | 86 | resource "tfe_policy_set" "sentinel" { 87 | name = "sentinel" 88 | description = "Policies that watch the watchman. Enforced only on the workspace that manages policies." 89 | organization = "${var.tfe_organization}" 90 | 91 | policy_ids = [ 92 | "${tfe_sentinel_policy.tfe_policies_only.id}", 93 | ] 94 | 95 | workspace_external_ids = [ 96 | "${local.workspaces["tfe-policies"]}", 97 | ] 98 | } 99 | 100 | # Test/experimental policies: 101 | 102 | resource "tfe_sentinel_policy" "passthrough" { 103 | name = "passthrough" 104 | description = "Just passing through! Always returns 'true'." 105 | organization = "${var.tfe_organization}" 106 | policy = "${file("./passthrough.sentinel")}" 107 | enforce_mode = "advisory" 108 | } 109 | 110 | # Sentinel management policies: 111 | 112 | resource "tfe_sentinel_policy" "tfe_policies_only" { 113 | name = "tfe_policies_only" 114 | description = "The Terraform config that manages Sentinel policies must not use the authenticated tfe provider to manage non-Sentinel resources." 115 | organization = "${var.tfe_organization}" 116 | policy = "${file("./tfe_policies_only.sentinel")}" 117 | enforce_mode = "hard-mandatory" 118 | } 119 | 120 | # Networking policies: 121 | 122 | resource "tfe_sentinel_policy" "aws-block-allow-all-cidr" { 123 | name = "aws-block-allow-all-cidr" 124 | description = "Avoid nasty firewall mistakes (AWS version)" 125 | organization = "${var.tfe_organization}" 126 | policy = "${file("./aws-block-allow-all-cidr.sentinel")}" 127 | enforce_mode = "hard-mandatory" 128 | } 129 | 130 | resource "tfe_sentinel_policy" "azurerm-block-allow-all-cidr" { 131 | name = "azurerm-block-allow-all-cidr" 132 | description = "Avoid nasty firewall mistakes (Azure version)" 133 | organization = "${var.tfe_organization}" 134 | policy = "${file("./azurerm-block-allow-all-cidr.sentinel")}" 135 | enforce_mode = "hard-mandatory" 136 | } 137 | 138 | resource "tfe_sentinel_policy" "gcp-block-allow-all-cidr" { 139 | name = "gcp-block-allow-all-cidr" 140 | description = "Avoid nasty firewall mistakes (GCP version)" 141 | organization = "${var.tfe_organization}" 142 | policy = "${file("./gcp-block-allow-all-cidr.sentinel")}" 143 | enforce_mode = "hard-mandatory" 144 | } 145 | 146 | # Compute instance policies: 147 | 148 | resource "tfe_sentinel_policy" "aws-restrict-instance-type-dev" { 149 | name = "aws-restrict-instance-type-dev" 150 | description = "Limit AWS instances to approved list (for dev infrastructure)" 151 | organization = "${var.tfe_organization}" 152 | policy = "${file("./aws-restrict-instance-type-dev.sentinel")}" 153 | enforce_mode = "soft-mandatory" 154 | } 155 | 156 | resource "tfe_sentinel_policy" "aws-restrict-instance-type-prod" { 157 | name = "aws-restrict-instance-type-prod" 158 | description = "Limit AWS instances to approved list (for prod infrastructure)" 159 | organization = "${var.tfe_organization}" 160 | policy = "${file("./aws-restrict-instance-type-prod.sentinel")}" 161 | enforce_mode = "soft-mandatory" 162 | } 163 | 164 | resource "tfe_sentinel_policy" "aws-restrict-instance-type-default" { 165 | name = "aws-restrict-instance-type-default" 166 | description = "Limit AWS instances to approved list" 167 | organization = "${var.tfe_organization}" 168 | policy = "${file("./aws-restrict-instance-type-default.sentinel")}" 169 | enforce_mode = "soft-mandatory" 170 | } 171 | 172 | resource "tfe_sentinel_policy" "azurerm-restrict-vm-size" { 173 | name = "azurerm-restrict-vm-size" 174 | description = "Limit Azure instances to approved list" 175 | organization = "${var.tfe_organization}" 176 | policy = "${file("./azurerm-restrict-vm-size.sentinel")}" 177 | enforce_mode = "soft-mandatory" 178 | } 179 | 180 | resource "tfe_sentinel_policy" "gcp-restrict-machine-type" { 181 | name = "gcp-restrict-machine-type" 182 | description = "Limit GCP instances to approved list" 183 | organization = "${var.tfe_organization}" 184 | policy = "${file("./gcp-restrict-machine-type.sentinel")}" 185 | enforce_mode = "soft-mandatory" 186 | } 187 | 188 | # Policy that requires modules to come from Private Module Registry 189 | data "template_file" "require-modules-from-pmr" { 190 | template = "${file("./require-modules-from-pmr.sentinel")}" 191 | 192 | vars { 193 | hostname = "${var.tfe_hostname}" 194 | organization = "${var.tfe_organization}" 195 | } 196 | } 197 | 198 | resource "tfe_sentinel_policy" "require-modules-from-pmr" { 199 | name = "require-modules-from-pmr" 200 | description = "Require all modules to come from the Private Module Registy of the current org" 201 | organization = "${var.tfe_organization}" 202 | policy = "${data.template_file.require-modules-from-pmr.rendered}" 203 | enforce_mode = "hard-mandatory" 204 | } 205 | -------------------------------------------------------------------------------- /passthrough.sentinel: -------------------------------------------------------------------------------- 1 | main = rule { 2 | true 3 | } -------------------------------------------------------------------------------- /require-modules-from-pmr.sentinel: -------------------------------------------------------------------------------- 1 | import "tfconfig" 2 | import "strings" 3 | 4 | # Note that this is a template fed to main.tf 5 | # But it can also be used with the Sentinel simulator 6 | # Do not change "app.terraform.io/OurOrganization" below 7 | # since that is what the tfconfig mocks use 8 | 9 | # Set module_prefix 10 | module_prefix = "${hostname}/${organization}" 11 | if strings.has_prefix(module_prefix, "$") { 12 | # template wasn't evaluated, probably in testing 13 | module_prefix = "app.terraform.io/OurOrganization" 14 | } 15 | 16 | # Require all modules directly under root module 17 | # to come from TFE private module registry (PMR) 18 | require_modules_from_pmr = rule { 19 | all tfconfig.modules as _, m { 20 | print("source: ", m.source) and 21 | strings.has_prefix(m.source, module_prefix) 22 | } 23 | } 24 | 25 | # Main rule that requires other rules to be true 26 | main = rule { 27 | (require_modules_from_pmr) else true 28 | } 29 | -------------------------------------------------------------------------------- /test/aws-block-allow-all-cidr/empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": { 3 | "main": true 4 | }, 5 | "mock": { 6 | "tfplan": { 7 | "resources": {} 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/aws-block-allow-all-cidr/fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": { 3 | "main": false 4 | }, 5 | "mock": { 6 | "tfplan": { 7 | "resources": { 8 | "aws_security_group": { 9 | "foo": { 10 | "0": { 11 | "applied": { 12 | "ingress": [{ "cidr_blocks": ["0.0.0.0/0"] }] 13 | } 14 | } 15 | } 16 | } 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/aws-block-allow-all-cidr/pass.json: -------------------------------------------------------------------------------- 1 | { 2 | "mock": { 3 | "tfplan": { 4 | "resources": { 5 | "aws_security_group": { 6 | "foo": { 7 | "0": { 8 | "applied": { 9 | "ingress": [{ "cidr_blocks": ["1.0.0.0/0"] }] 10 | } 11 | } 12 | } 13 | } 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/aws-restrict-instance-type-default/dev-not-prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "global":{}, 3 | "test": { 4 | "main": true, 5 | "instance_type_allowed": true 6 | }, 7 | "mock": { 8 | "tfplan": { 9 | "resources": { 10 | "aws_instance": { 11 | "ok-in-dev": { 12 | "0": { 13 | "applied": { 14 | "ami": "ami-0afae182eed9d2b46", 15 | "instance_type": "t2.micro", 16 | "tags": { 17 | "Name": "HelloWorld" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/aws-restrict-instance-type-default/fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "global":{}, 3 | "test": { 4 | "main": false, 5 | "instance_type_allowed": false 6 | }, 7 | "mock": { 8 | "tfplan": { 9 | "resources": { 10 | "aws_instance": { 11 | "always-bad": { 12 | "0": { 13 | "applied": { 14 | "ami": "ami-0afae182eed9d2b46", 15 | "instance_type": "t3.2xlarge", 16 | "tags": { 17 | "Name": "HelloWorld" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/aws-restrict-instance-type-default/pass.json: -------------------------------------------------------------------------------- 1 | { 2 | "global":{}, 3 | "test": { 4 | "main": true, 5 | "instance_type_allowed": true 6 | }, 7 | "mock": { 8 | "tfplan": { 9 | "resources": { 10 | "aws_instance": { 11 | "always-good": { 12 | "0": { 13 | "applied": { 14 | "ami": "ami-0afae182eed9d2b46", 15 | "instance_type": "t2.medium", 16 | "tags": { 17 | "Name": "HelloWorld" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/aws-restrict-instance-type-default/prod-not-dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "global":{}, 3 | "test": { 4 | "main": true, 5 | "instance_type_allowed": true 6 | }, 7 | "mock": { 8 | "tfplan": { 9 | "resources": { 10 | "aws_instance": { 11 | "ok-in-prod": { 12 | "0": { 13 | "applied": { 14 | "ami": "ami-0afae182eed9d2b46", 15 | "instance_type": "t2.xlarge", 16 | "tags": { 17 | "Name": "HelloWorld" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/aws-restrict-instance-type-dev/fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "global":{}, 3 | "test": { 4 | "main": false, 5 | "instance_type_allowed": false 6 | }, 7 | "mock": { 8 | "tfplan": { 9 | "resources": { 10 | "aws_instance": { 11 | "always-bad": { 12 | "0": { 13 | "applied": { 14 | "ami": "ami-0afae182eed9d2b46", 15 | "instance_type": "t3.2xlarge", 16 | "tags": { 17 | "Name": "HelloWorld" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/aws-restrict-instance-type-dev/pass.json: -------------------------------------------------------------------------------- 1 | { 2 | "global":{}, 3 | "test": { 4 | "main": true, 5 | "instance_type_allowed": true 6 | }, 7 | "mock": { 8 | "tfplan": { 9 | "resources": { 10 | "aws_instance": { 11 | "always-good": { 12 | "0": { 13 | "applied": { 14 | "ami": "ami-0afae182eed9d2b46", 15 | "instance_type": "t2.medium", 16 | "tags": { 17 | "Name": "HelloWorld" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/aws-restrict-instance-type-dev/prod-not-dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "global":{}, 3 | "test": { 4 | "main": false, 5 | "instance_type_allowed": false 6 | }, 7 | "mock": { 8 | "tfplan": { 9 | "resources": { 10 | "aws_instance": { 11 | "ok-in-prod": { 12 | "0": { 13 | "applied": { 14 | "ami": "ami-0afae182eed9d2b46", 15 | "instance_type": "t2.xlarge", 16 | "tags": { 17 | "Name": "HelloWorld" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/aws-restrict-instance-type-prod/dev-not-prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "global":{}, 3 | "test": { 4 | "main": false, 5 | "instance_type_allowed": false 6 | }, 7 | "mock": { 8 | "tfplan": { 9 | "resources": { 10 | "aws_instance": { 11 | "ok-in-dev": { 12 | "0": { 13 | "applied": { 14 | "ami": "ami-0afae182eed9d2b46", 15 | "instance_type": "t2.micro", 16 | "tags": { 17 | "Name": "HelloWorld" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/aws-restrict-instance-type-prod/fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "global":{}, 3 | "test": { 4 | "main": false, 5 | "instance_type_allowed": false 6 | }, 7 | "mock": { 8 | "tfplan": { 9 | "resources": { 10 | "aws_instance": { 11 | "always-bad": { 12 | "0": { 13 | "applied": { 14 | "ami": "ami-0afae182eed9d2b46", 15 | "instance_type": "t3.2xlarge", 16 | "tags": { 17 | "Name": "HelloWorld" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/aws-restrict-instance-type-prod/pass.json: -------------------------------------------------------------------------------- 1 | { 2 | "global":{}, 3 | "test": { 4 | "main": true, 5 | "instance_type_allowed": true 6 | }, 7 | "mock": { 8 | "tfplan": { 9 | "resources": { 10 | "aws_instance": { 11 | "always-good": { 12 | "0": { 13 | "applied": { 14 | "ami": "ami-0afae182eed9d2b46", 15 | "instance_type": "t2.medium", 16 | "tags": { 17 | "Name": "HelloWorld" 18 | } 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/azurerm-block-allow-all-cidr/fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": { 3 | "main": false 4 | }, 5 | "mock": { 6 | "tfplan": { 7 | "resources": { 8 | "azurerm_network_security_group": { 9 | "foo": { 10 | "0": { 11 | "applied": { 12 | "security_rule": [ 13 | { 14 | "source_address_prefix": "0.0.0.0/0", 15 | "access": "Allow" 16 | } 17 | ] 18 | } 19 | } 20 | }, 21 | "bar": { 22 | "0": { 23 | "applied": { 24 | "security_rule": [ 25 | { 26 | "source_address_prefix": "*", 27 | "access": "Allow" 28 | } 29 | ] 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/azurerm-block-allow-all-cidr/pass.json: -------------------------------------------------------------------------------- 1 | { 2 | "mock": { 3 | "tfplan": { 4 | "resources": { 5 | "azurerm_network_security_group": { 6 | "foo": { 7 | "0": { 8 | "applied": { 9 | "security_rule": [ 10 | { 11 | "source_address_prefix": "1.0.0.0/0", 12 | "access": "Allow" 13 | }, 14 | { 15 | "source_address_prefix": "0.0.0.0/0", 16 | "access": "Deny" 17 | } 18 | ] 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/azurerm-restrict-vm-size/fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "global":{}, 3 | "test": { 4 | "main": false, 5 | "instance_type_allowed": false 6 | }, 7 | "mock": { 8 | "tfplan": { 9 | "resources": { 10 | "azurerm_virtual_machine": { 11 | "always-bad": { 12 | "0": { 13 | "applied": { 14 | "vm_size": "Standard_D3" 15 | } 16 | } 17 | } 18 | } 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/azurerm-restrict-vm-size/pass.json: -------------------------------------------------------------------------------- 1 | { 2 | "global":{}, 3 | "mock": { 4 | "tfplan": { 5 | "resources": { 6 | "azurerm_virtual_machine": { 7 | "always-bad": { 8 | "0": { 9 | "applied": { 10 | "vm_size": "Standard_D1_v2" 11 | } 12 | } 13 | } 14 | } 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/gcp-block-allow-all-cidr/fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": { 3 | "main": false 4 | }, 5 | "mock": { 6 | "tfplan": { 7 | "resources": { 8 | "google_compute_firewall": { 9 | "foo": { 10 | "0": { 11 | "applied": { 12 | "source_ranges": ["0.0.0.0/0"] 13 | } 14 | } 15 | } 16 | } 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/gcp-block-allow-all-cidr/pass.json: -------------------------------------------------------------------------------- 1 | { 2 | "mock": { 3 | "tfplan": { 4 | "resources": { 5 | "google_compute_firewall": { 6 | "foo": { 7 | "0": { 8 | "applied": { 9 | "source_ranges": ["1.0.0.0/0"] 10 | } 11 | } 12 | } 13 | } 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/gcp-restrict-machine-type/fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": { 3 | "main": false 4 | }, 5 | "mock": { 6 | "tfplan": { 7 | "resources": { 8 | "google_compute_instance": { 9 | "foo": { 10 | "0": { 11 | "applied": { 12 | "machine_type": "n1-standard-5" 13 | } 14 | } 15 | } 16 | } 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/gcp-restrict-machine-type/pass.json: -------------------------------------------------------------------------------- 1 | { 2 | "mock": { 3 | "tfplan": { 4 | "resources": { 5 | "google_compute_instance": { 6 | "foo": { 7 | "0": { 8 | "applied": { 9 | "machine_type": "n1-standard-1" 10 | } 11 | } 12 | } 13 | } 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/passthrough/pass.json: -------------------------------------------------------------------------------- 1 | { 2 | "global": { 3 | } 4 | } -------------------------------------------------------------------------------- /test/require-modules-from-pmr/fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "mock": { 3 | "tfconfig": "tfconfig-fail.sentinel" 4 | }, 5 | "test": { 6 | "main": false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/require-modules-from-pmr/pass.json: -------------------------------------------------------------------------------- 1 | { 2 | "mock": { 3 | "tfconfig": "tfconfig-pass.sentinel" 4 | }, 5 | "test": { 6 | "main": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/require-modules-from-pmr/tfconfig-fail.sentinel: -------------------------------------------------------------------------------- 1 | _root = { 2 | "data": {}, 3 | "modules": { 4 | "network": { 5 | "config": { 6 | "allow_ssh_traffic": "1", 7 | "location": "${var.location}", 8 | "resource_group_name": "${var.windows_dns_prefix}-rc", 9 | }, 10 | "source": "Azure/network/azurerm", 11 | "version": "1.1.1", 12 | }, 13 | "windowsserver": { 14 | "config": { 15 | "admin_password": "${var.admin_password}", 16 | "location": "${var.location}", 17 | "public_ip_dns": [ 18 | "${var.windows_dns_prefix}", 19 | ], 20 | "resource_group_name": "${var.windows_dns_prefix}-rc", 21 | "vm_hostname": "demohost", 22 | "vm_os_simple": "WindowsServer", 23 | "vnet_subnet_id": "${module.network.vnet_subnets[0]}", 24 | }, 25 | "source": "Azure/compute/azurerm", 26 | "version": "1.1.5", 27 | }, 28 | }, 29 | "outputs": { 30 | "windows_vm_public_name": { 31 | "depends_on": [], 32 | "description": "", 33 | "sensitive": false, 34 | "value": "${module.windowsserver.public_ip_dns_name}", 35 | }, 36 | }, 37 | "providers": {}, 38 | "resources": {}, 39 | "variables": { 40 | "admin_password": { 41 | "default": "pTFE1234!", 42 | "description": "admin password for Windows VM", 43 | }, 44 | "location": { 45 | "default": "East US", 46 | "description": "Azure location in which to create resources", 47 | }, 48 | "windows_dns_prefix": { 49 | "default": null, 50 | "description": "DNS prefix to add to to public IP address for Windows VM", 51 | }, 52 | }, 53 | } 54 | 55 | module_network = { 56 | "data": {}, 57 | "modules": {}, 58 | "outputs": { 59 | "security_group_id": { 60 | "depends_on": [], 61 | "description": "The id of the security group attached to subnets inside the newly created vNet. Use this id to associate additional network security rules to subnets.", 62 | "sensitive": false, 63 | "value": "${azurerm_network_security_group.security_group.id}", 64 | }, 65 | "vnet_address_space": { 66 | "depends_on": [], 67 | "description": "The address space of the newly created vNet", 68 | "sensitive": false, 69 | "value": "${azurerm_virtual_network.vnet.address_space}", 70 | }, 71 | "vnet_id": { 72 | "depends_on": [], 73 | "description": "The id of the newly created vNet", 74 | "sensitive": false, 75 | "value": "${azurerm_virtual_network.vnet.id}", 76 | }, 77 | "vnet_location": { 78 | "depends_on": [], 79 | "description": "The location of the newly created vNet", 80 | "sensitive": false, 81 | "value": "${azurerm_virtual_network.vnet.location}", 82 | }, 83 | "vnet_name": { 84 | "depends_on": [], 85 | "description": "The Name of the newly created vNet", 86 | "sensitive": false, 87 | "value": "${azurerm_virtual_network.vnet.name}", 88 | }, 89 | "vnet_subnets": { 90 | "depends_on": [], 91 | "description": "The ids of subnets created inside the newl vNet", 92 | "sensitive": false, 93 | "value": "${azurerm_subnet.subnet.*.id}", 94 | }, 95 | }, 96 | "providers": {}, 97 | "resources": { 98 | "azurerm_network_security_group": { 99 | "security_group": { 100 | "config": { 101 | "location": "${var.location}", 102 | "name": "${var.sg_name}", 103 | "resource_group_name": "${azurerm_resource_group.network.name}", 104 | "tags": "${var.tags}", 105 | }, 106 | "provisioners": null, 107 | }, 108 | }, 109 | "azurerm_network_security_rule": { 110 | "security_rule_rdp": { 111 | "config": { 112 | "access": "Allow", 113 | "destination_address_prefix": "*", 114 | "destination_port_range": "3389", 115 | "direction": "Inbound", 116 | "name": "rdp", 117 | "network_security_group_name": "${azurerm_network_security_group.security_group.name}", 118 | "priority": 101, 119 | "protocol": "Tcp", 120 | "resource_group_name": "${azurerm_resource_group.network.name}", 121 | "source_address_prefix": "*", 122 | "source_port_range": "*", 123 | }, 124 | "provisioners": null, 125 | }, 126 | "security_rule_ssh": { 127 | "config": { 128 | "access": "Allow", 129 | "destination_address_prefix": "*", 130 | "destination_port_range": "22", 131 | "direction": "Inbound", 132 | "name": "ssh", 133 | "network_security_group_name": "${azurerm_network_security_group.security_group.name}", 134 | "priority": 102, 135 | "protocol": "Tcp", 136 | "resource_group_name": "${azurerm_resource_group.network.name}", 137 | "source_address_prefix": "*", 138 | "source_port_range": "*", 139 | }, 140 | "provisioners": null, 141 | }, 142 | }, 143 | "azurerm_resource_group": { 144 | "network": { 145 | "config": { 146 | "location": "${var.location}", 147 | "name": "${var.resource_group_name}", 148 | }, 149 | "provisioners": null, 150 | }, 151 | }, 152 | "azurerm_subnet": { 153 | "subnet": { 154 | "config": { 155 | "address_prefix": "${var.subnet_prefixes[count.index]}", 156 | "name": "${var.subnet_names[count.index]}", 157 | "network_security_group_id": "${azurerm_network_security_group.security_group.id}", 158 | "resource_group_name": "${azurerm_resource_group.network.name}", 159 | "virtual_network_name": "${azurerm_virtual_network.vnet.name}", 160 | }, 161 | "provisioners": null, 162 | }, 163 | }, 164 | "azurerm_virtual_network": { 165 | "vnet": { 166 | "config": { 167 | "address_space": [ 168 | "${var.address_space}", 169 | ], 170 | "dns_servers": "${var.dns_servers}", 171 | "location": "${var.location}", 172 | "name": "${var.vnet_name}", 173 | "resource_group_name": "${azurerm_resource_group.network.name}", 174 | "tags": "${var.tags}", 175 | }, 176 | "provisioners": null, 177 | }, 178 | }, 179 | }, 180 | "variables": { 181 | "address_space": { 182 | "default": "10.0.0.0/16", 183 | "description": "The address space that is used by the virtual network.", 184 | }, 185 | "allow_rdp_traffic": { 186 | "default": "0", 187 | "description": "This optional variable, when set to true, adds a security rule allowing RDP traffic to flow through to the newly created network. The default value is false.", 188 | }, 189 | "allow_ssh_traffic": { 190 | "default": "0", 191 | "description": "This optional variable, when set to true, adds a security rule allowing SSH traffic to flow through to the newly created network. The default value is false.", 192 | }, 193 | "dns_servers": { 194 | "default": [], 195 | "description": "The DNS servers to be used with vNet.", 196 | }, 197 | "location": { 198 | "default": null, 199 | "description": "The location/region where the core network will be created. The full list of Azure regions can be found at https://azure.microsoft.com/regions", 200 | }, 201 | "resource_group_name": { 202 | "default": "myapp-rg", 203 | "description": "Default resource group name that the network will be created in.", 204 | }, 205 | "sg_name": { 206 | "default": "acctsecgrp", 207 | "description": "Give a name to security group", 208 | }, 209 | "subnet_names": { 210 | "default": [ 211 | "subnet1", 212 | ], 213 | "description": "A list of public subnets inside the vNet.", 214 | }, 215 | "subnet_prefixes": { 216 | "default": [ 217 | "10.0.1.0/24", 218 | ], 219 | "description": "The address prefix to use for the subnet.", 220 | }, 221 | "tags": { 222 | "default": { 223 | "tag1": "", 224 | "tag2": "", 225 | }, 226 | "description": "The tags to associate with your network and subnets.", 227 | }, 228 | "vnet_name": { 229 | "default": "acctvnet", 230 | "description": "Name of the vnet to create", 231 | }, 232 | }, 233 | } 234 | 235 | module_windowsserver = { 236 | "data": {}, 237 | "modules": { 238 | "os": { 239 | "config": { 240 | "vm_os_simple": "${var.vm_os_simple}", 241 | }, 242 | "source": "./os", 243 | "version": "", 244 | }, 245 | }, 246 | "outputs": { 247 | "availability_set_id": { 248 | "depends_on": [], 249 | "description": "id of the availability set where the vms are provisioned.", 250 | "sensitive": false, 251 | "value": "${azurerm_availability_set.vm.id}", 252 | }, 253 | "network_interface_ids": { 254 | "depends_on": [], 255 | "description": "ids of the vm nics provisoned.", 256 | "sensitive": false, 257 | "value": "${azurerm_network_interface.vm.*.id}", 258 | }, 259 | "network_interface_private_ip": { 260 | "depends_on": [], 261 | "description": "private ip addresses of the vm nics", 262 | "sensitive": false, 263 | "value": "${azurerm_network_interface.vm.*.private_ip_address}", 264 | }, 265 | "network_security_group_id": { 266 | "depends_on": [], 267 | "description": "id of the security group provisioned", 268 | "sensitive": false, 269 | "value": "${azurerm_network_security_group.vm.id}", 270 | }, 271 | "public_ip_address": { 272 | "depends_on": [], 273 | "description": "The actual ip address allocated for the resource.", 274 | "sensitive": false, 275 | "value": "${azurerm_public_ip.vm.*.ip_address}", 276 | }, 277 | "public_ip_dns_name": { 278 | "depends_on": [], 279 | "description": "fqdn to connect to the first vm provisioned.", 280 | "sensitive": false, 281 | "value": "${azurerm_public_ip.vm.*.fqdn}", 282 | }, 283 | "public_ip_id": { 284 | "depends_on": [], 285 | "description": "id of the public ip address provisoned.", 286 | "sensitive": false, 287 | "value": "${azurerm_public_ip.vm.*.id}", 288 | }, 289 | "vm_ids": { 290 | "depends_on": [], 291 | "description": "Virtual machine ids created.", 292 | "sensitive": false, 293 | "value": "${concat(azurerm_virtual_machine.vm-windows.*.id, azurerm_virtual_machine.vm-linux.*.id)}", 294 | }, 295 | }, 296 | "providers": { 297 | "azurerm": { 298 | "alias": {}, 299 | "config": {}, 300 | "version": "~> 0.3", 301 | }, 302 | "random": { 303 | "alias": {}, 304 | "config": {}, 305 | "version": "~> 1.0", 306 | }, 307 | }, 308 | "resources": { 309 | "azurerm_availability_set": { 310 | "vm": { 311 | "config": { 312 | "location": "${azurerm_resource_group.vm.location}", 313 | "managed": true, 314 | "name": "${var.vm_hostname}-avset", 315 | "platform_fault_domain_count": 2, 316 | "platform_update_domain_count": 2, 317 | "resource_group_name": "${azurerm_resource_group.vm.name}", 318 | }, 319 | "provisioners": null, 320 | }, 321 | }, 322 | "azurerm_network_interface": { 323 | "vm": { 324 | "config": { 325 | "ip_configuration": [ 326 | { 327 | "name": "ipconfig${count.index}", 328 | "private_ip_address_allocation": "Dynamic", 329 | "public_ip_address_id": "${length(azurerm_public_ip.vm.*.id) > 0 ? element(concat(azurerm_public_ip.vm.*.id, list(\"\")), count.index) : \"\"}", 330 | "subnet_id": "${var.vnet_subnet_id}", 331 | }, 332 | ], 333 | "location": "${azurerm_resource_group.vm.location}", 334 | "name": "nic-${var.vm_hostname}-${count.index}", 335 | "network_security_group_id": "${azurerm_network_security_group.vm.id}", 336 | "resource_group_name": "${azurerm_resource_group.vm.name}", 337 | }, 338 | "provisioners": null, 339 | }, 340 | }, 341 | "azurerm_network_security_group": { 342 | "vm": { 343 | "config": { 344 | "location": "${azurerm_resource_group.vm.location}", 345 | "name": "${var.vm_hostname}-${coalesce(var.remote_port,module.os.calculated_remote_port)}-nsg", 346 | "resource_group_name": "${azurerm_resource_group.vm.name}", 347 | "security_rule": [ 348 | { 349 | "access": "Allow", 350 | "description": "Allow remote protocol in from all locations", 351 | "destination_address_prefix": "*", 352 | "destination_port_range": "${coalesce(var.remote_port,module.os.calculated_remote_port)}", 353 | "direction": "Inbound", 354 | "name": "allow_remote_${coalesce(var.remote_port,module.os.calculated_remote_port)}_in_all", 355 | "priority": 100, 356 | "protocol": "Tcp", 357 | "source_address_prefix": "*", 358 | "source_port_range": "*", 359 | }, 360 | ], 361 | }, 362 | "provisioners": null, 363 | }, 364 | }, 365 | "azurerm_public_ip": { 366 | "vm": { 367 | "config": { 368 | "domain_name_label": "${element(var.public_ip_dns, count.index)}", 369 | "location": "${var.location}", 370 | "name": "${var.vm_hostname}-${count.index}-publicIP", 371 | "public_ip_address_allocation": "${var.public_ip_address_allocation}", 372 | "resource_group_name": "${azurerm_resource_group.vm.name}", 373 | }, 374 | "provisioners": null, 375 | }, 376 | }, 377 | "azurerm_resource_group": { 378 | "vm": { 379 | "config": { 380 | "location": "${var.location}", 381 | "name": "${var.resource_group_name}", 382 | "tags": "${var.tags}", 383 | }, 384 | "provisioners": null, 385 | }, 386 | }, 387 | "azurerm_storage_account": { 388 | "vm-sa": { 389 | "config": { 390 | "account_replication_type": "${element(split(\"_\", var.boot_diagnostics_sa_type),1)}", 391 | "account_tier": "${element(split(\"_\", var.boot_diagnostics_sa_type),0)}", 392 | "location": "${var.location}", 393 | "name": "bootdiag${lower(random_id.vm-sa.hex)}", 394 | "resource_group_name": "${azurerm_resource_group.vm.name}", 395 | "tags": "${var.tags}", 396 | }, 397 | "provisioners": null, 398 | }, 399 | }, 400 | "azurerm_virtual_machine": { 401 | "vm-linux": { 402 | "config": { 403 | "availability_set_id": "${azurerm_availability_set.vm.id}", 404 | "boot_diagnostics": [ 405 | { 406 | "enabled": "${var.boot_diagnostics}", 407 | "storage_uri": "${var.boot_diagnostics == \"true\" ? join(\",\", azurerm_storage_account.vm-sa.*.primary_blob_endpoint) : \"\" }", 408 | }, 409 | ], 410 | "delete_os_disk_on_termination": "${var.delete_os_disk_on_termination}", 411 | "location": "${var.location}", 412 | "name": "${var.vm_hostname}${count.index}", 413 | "network_interface_ids": [ 414 | "${element(azurerm_network_interface.vm.*.id, count.index)}", 415 | ], 416 | "os_profile": [ 417 | { 418 | "admin_password": "${var.admin_password}", 419 | "admin_username": "${var.admin_username}", 420 | "computer_name": "${var.vm_hostname}${count.index}", 421 | }, 422 | ], 423 | "os_profile_linux_config": [ 424 | { 425 | "disable_password_authentication": true, 426 | "ssh_keys": [ 427 | { 428 | "key_data": "${file(\"${var.ssh_key}\")}", 429 | "path": "/home/${var.admin_username}/.ssh/authorized_keys", 430 | }, 431 | ], 432 | }, 433 | ], 434 | "resource_group_name": "${azurerm_resource_group.vm.name}", 435 | "storage_image_reference": [ 436 | { 437 | "id": "${var.vm_os_id}", 438 | "offer": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_offer, module.os.calculated_value_os_offer) : \"\"}", 439 | "publisher": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_publisher, module.os.calculated_value_os_publisher) : \"\"}", 440 | "sku": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_sku, module.os.calculated_value_os_sku) : \"\"}", 441 | "version": "${var.vm_os_id == \"\" ? var.vm_os_version : \"\"}", 442 | }, 443 | ], 444 | "storage_os_disk": [ 445 | { 446 | "caching": "ReadWrite", 447 | "create_option": "FromImage", 448 | "managed_disk_type": "${var.storage_account_type}", 449 | "name": "osdisk-${var.vm_hostname}-${count.index}", 450 | }, 451 | ], 452 | "tags": "${var.tags}", 453 | "vm_size": "${var.vm_size}", 454 | }, 455 | "provisioners": null, 456 | }, 457 | "vm-linux-with-datadisk": { 458 | "config": { 459 | "availability_set_id": "${azurerm_availability_set.vm.id}", 460 | "boot_diagnostics": [ 461 | { 462 | "enabled": "${var.boot_diagnostics}", 463 | "storage_uri": "${var.boot_diagnostics == \"true\" ? join(\",\", azurerm_storage_account.vm-sa.*.primary_blob_endpoint) : \"\" }", 464 | }, 465 | ], 466 | "delete_os_disk_on_termination": "${var.delete_os_disk_on_termination}", 467 | "location": "${var.location}", 468 | "name": "${var.vm_hostname}${count.index}", 469 | "network_interface_ids": [ 470 | "${element(azurerm_network_interface.vm.*.id, count.index)}", 471 | ], 472 | "os_profile": [ 473 | { 474 | "admin_password": "${var.admin_password}", 475 | "admin_username": "${var.admin_username}", 476 | "computer_name": "${var.vm_hostname}${count.index}", 477 | }, 478 | ], 479 | "os_profile_linux_config": [ 480 | { 481 | "disable_password_authentication": true, 482 | "ssh_keys": [ 483 | { 484 | "key_data": "${file(\"${var.ssh_key}\")}", 485 | "path": "/home/${var.admin_username}/.ssh/authorized_keys", 486 | }, 487 | ], 488 | }, 489 | ], 490 | "resource_group_name": "${azurerm_resource_group.vm.name}", 491 | "storage_data_disk": [ 492 | { 493 | "create_option": "Empty", 494 | "disk_size_gb": "${var.data_disk_size_gb}", 495 | "lun": 0, 496 | "managed_disk_type": "${var.data_sa_type}", 497 | "name": "datadisk-${var.vm_hostname}-${count.index}", 498 | }, 499 | ], 500 | "storage_image_reference": [ 501 | { 502 | "id": "${var.vm_os_id}", 503 | "offer": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_offer, module.os.calculated_value_os_offer) : \"\"}", 504 | "publisher": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_publisher, module.os.calculated_value_os_publisher) : \"\"}", 505 | "sku": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_sku, module.os.calculated_value_os_sku) : \"\"}", 506 | "version": "${var.vm_os_id == \"\" ? var.vm_os_version : \"\"}", 507 | }, 508 | ], 509 | "storage_os_disk": [ 510 | { 511 | "caching": "ReadWrite", 512 | "create_option": "FromImage", 513 | "managed_disk_type": "${var.storage_account_type}", 514 | "name": "osdisk-${var.vm_hostname}-${count.index}", 515 | }, 516 | ], 517 | "tags": "${var.tags}", 518 | "vm_size": "${var.vm_size}", 519 | }, 520 | "provisioners": null, 521 | }, 522 | "vm-windows": { 523 | "config": { 524 | "availability_set_id": "${azurerm_availability_set.vm.id}", 525 | "boot_diagnostics": [ 526 | { 527 | "enabled": "${var.boot_diagnostics}", 528 | "storage_uri": "${var.boot_diagnostics == \"true\" ? join(\",\", azurerm_storage_account.vm-sa.*.primary_blob_endpoint) : \"\" }", 529 | }, 530 | ], 531 | "delete_os_disk_on_termination": "${var.delete_os_disk_on_termination}", 532 | "location": "${var.location}", 533 | "name": "${var.vm_hostname}${count.index}", 534 | "network_interface_ids": [ 535 | "${element(azurerm_network_interface.vm.*.id, count.index)}", 536 | ], 537 | "os_profile": [ 538 | { 539 | "admin_password": "${var.admin_password}", 540 | "admin_username": "${var.admin_username}", 541 | "computer_name": "${var.vm_hostname}${count.index}", 542 | }, 543 | ], 544 | "os_profile_windows_config": [ 545 | {}, 546 | ], 547 | "resource_group_name": "${azurerm_resource_group.vm.name}", 548 | "storage_image_reference": [ 549 | { 550 | "id": "${var.vm_os_id}", 551 | "offer": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_offer, module.os.calculated_value_os_offer) : \"\"}", 552 | "publisher": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_publisher, module.os.calculated_value_os_publisher) : \"\"}", 553 | "sku": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_sku, module.os.calculated_value_os_sku) : \"\"}", 554 | "version": "${var.vm_os_id == \"\" ? var.vm_os_version : \"\"}", 555 | }, 556 | ], 557 | "storage_os_disk": [ 558 | { 559 | "caching": "ReadWrite", 560 | "create_option": "FromImage", 561 | "managed_disk_type": "${var.storage_account_type}", 562 | "name": "osdisk-${var.vm_hostname}-${count.index}", 563 | }, 564 | ], 565 | "tags": "${var.tags}", 566 | "vm_size": "${var.vm_size}", 567 | }, 568 | "provisioners": null, 569 | }, 570 | "vm-windows-with-datadisk": { 571 | "config": { 572 | "availability_set_id": "${azurerm_availability_set.vm.id}", 573 | "boot_diagnostics": [ 574 | { 575 | "enabled": "${var.boot_diagnostics}", 576 | "storage_uri": "${var.boot_diagnostics == \"true\" ? join(\",\", azurerm_storage_account.vm-sa.*.primary_blob_endpoint) : \"\" }", 577 | }, 578 | ], 579 | "delete_os_disk_on_termination": "${var.delete_os_disk_on_termination}", 580 | "location": "${var.location}", 581 | "name": "${var.vm_hostname}${count.index}", 582 | "network_interface_ids": [ 583 | "${element(azurerm_network_interface.vm.*.id, count.index)}", 584 | ], 585 | "os_profile": [ 586 | { 587 | "admin_password": "${var.admin_password}", 588 | "admin_username": "${var.admin_username}", 589 | "computer_name": "${var.vm_hostname}${count.index}", 590 | }, 591 | ], 592 | "os_profile_windows_config": [ 593 | {}, 594 | ], 595 | "resource_group_name": "${azurerm_resource_group.vm.name}", 596 | "storage_data_disk": [ 597 | { 598 | "create_option": "Empty", 599 | "disk_size_gb": "${var.data_disk_size_gb}", 600 | "lun": 0, 601 | "managed_disk_type": "${var.data_sa_type}", 602 | "name": "datadisk-${var.vm_hostname}-${count.index}", 603 | }, 604 | ], 605 | "storage_image_reference": [ 606 | { 607 | "id": "${var.vm_os_id}", 608 | "offer": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_offer, module.os.calculated_value_os_offer) : \"\"}", 609 | "publisher": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_publisher, module.os.calculated_value_os_publisher) : \"\"}", 610 | "sku": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_sku, module.os.calculated_value_os_sku) : \"\"}", 611 | "version": "${var.vm_os_id == \"\" ? var.vm_os_version : \"\"}", 612 | }, 613 | ], 614 | "storage_os_disk": [ 615 | { 616 | "caching": "ReadWrite", 617 | "create_option": "FromImage", 618 | "managed_disk_type": "${var.storage_account_type}", 619 | "name": "osdisk-${var.vm_hostname}-${count.index}", 620 | }, 621 | ], 622 | "tags": "${var.tags}", 623 | "vm_size": "${var.vm_size}", 624 | }, 625 | "provisioners": null, 626 | }, 627 | }, 628 | "random_id": { 629 | "vm-sa": { 630 | "config": { 631 | "byte_length": 6, 632 | "keepers": [ 633 | { 634 | "vm_hostname": "${var.vm_hostname}", 635 | }, 636 | ], 637 | }, 638 | "provisioners": null, 639 | }, 640 | }, 641 | }, 642 | "variables": { 643 | "admin_password": { 644 | "default": "", 645 | "description": "The admin password to be used on the VMSS that will be deployed. The password must meet the complexity requirements of Azure", 646 | }, 647 | "admin_username": { 648 | "default": "azureuser", 649 | "description": "The admin username of the VM that will be deployed", 650 | }, 651 | "boot_diagnostics": { 652 | "default": "false", 653 | "description": "(Optional) Enable or Disable boot diagnostics", 654 | }, 655 | "boot_diagnostics_sa_type": { 656 | "default": "Standard_LRS", 657 | "description": "(Optional) Storage account type for boot diagnostics", 658 | }, 659 | "data_disk": { 660 | "default": "false", 661 | "description": "Set to true to add a datadisk.", 662 | }, 663 | "data_disk_size_gb": { 664 | "default": "", 665 | "description": "Storage data disk size size", 666 | }, 667 | "data_sa_type": { 668 | "default": "Standard_LRS", 669 | "description": "Data Disk Storage Account type", 670 | }, 671 | "delete_os_disk_on_termination": { 672 | "default": "false", 673 | "description": "Delete datadisk when machine is terminated", 674 | }, 675 | "is_windows_image": { 676 | "default": "false", 677 | "description": "Boolean flag to notify when the custom image is windows based. Only used in conjunction with vm_os_id", 678 | }, 679 | "location": { 680 | "default": null, 681 | "description": "The location/region where the virtual network is created. Changing this forces a new resource to be created.", 682 | }, 683 | "nb_instances": { 684 | "default": "1", 685 | "description": "Specify the number of vm instances", 686 | }, 687 | "nb_public_ip": { 688 | "default": "1", 689 | "description": "Number of public IPs to assign corresponding to one IP per vm. Set to 0 to not assign any public IP addresses.", 690 | }, 691 | "public_ip_address_allocation": { 692 | "default": "dynamic", 693 | "description": "Defines how an IP address is assigned. Options are Static or Dynamic.", 694 | }, 695 | "public_ip_dns": { 696 | "default": [ 697 | "", 698 | ], 699 | "description": "Optional globally unique per datacenter region domain name label to apply to each public ip address. e.g. thisvar.varlocation.cloudapp.azure.com where you specify only thisvar here. This is an array of names which will pair up sequentially to the number of public ips defined in var.nb_public_ip. One name or empty string is required for every public ip. If no public ip is desired, then set this to an array with a single empty string.", 700 | }, 701 | "remote_port": { 702 | "default": "", 703 | "description": "Remote tcp port to be used for access to the vms created via the nsg applied to the nics.", 704 | }, 705 | "resource_group_name": { 706 | "default": "terraform-compute", 707 | "description": "The name of the resource group in which the resources will be created", 708 | }, 709 | "ssh_key": { 710 | "default": "~/.ssh/id_rsa.pub", 711 | "description": "Path to the public key to be used for ssh access to the VM. Only used with non-Windows vms and can be left as-is even if using Windows vms. If specifying a path to a certification on a Windows machine to provision a linux vm use the / in the path versus backslash. e.g. c:/home/id_rsa.pub", 712 | }, 713 | "storage_account_type": { 714 | "default": "Premium_LRS", 715 | "description": "Defines the type of storage account to be created. Valid options are Standard_LRS, Standard_ZRS, Standard_GRS, Standard_RAGRS, Premium_LRS.", 716 | }, 717 | "tags": { 718 | "default": { 719 | "source": "terraform", 720 | }, 721 | "description": "A map of the tags to use on the resources that are deployed with this module.", 722 | }, 723 | "vm_hostname": { 724 | "default": "myvm", 725 | "description": "local name of the VM", 726 | }, 727 | "vm_os_id": { 728 | "default": "", 729 | "description": "The resource ID of the image that you want to deploy if you are using a custom image.Note, need to provide is_windows_image = true for windows custom images.", 730 | }, 731 | "vm_os_offer": { 732 | "default": "", 733 | "description": "The name of the offer of the image that you want to deploy. This is ignored when vm_os_id or vm_os_simple are provided.", 734 | }, 735 | "vm_os_publisher": { 736 | "default": "", 737 | "description": "The name of the publisher of the image that you want to deploy. This is ignored when vm_os_id or vm_os_simple are provided.", 738 | }, 739 | "vm_os_simple": { 740 | "default": "", 741 | "description": "Specify UbuntuServer, WindowsServer, RHEL, openSUSE-Leap, CentOS, Debian, CoreOS and SLES to get the latest image version of the specified os. Do not provide this value if a custom value is used for vm_os_publisher, vm_os_offer, and vm_os_sku.", 742 | }, 743 | "vm_os_sku": { 744 | "default": "", 745 | "description": "The sku of the image that you want to deploy. This is ignored when vm_os_id or vm_os_simple are provided.", 746 | }, 747 | "vm_os_version": { 748 | "default": "latest", 749 | "description": "The version of the image that you want to deploy. This is ignored when vm_os_id or vm_os_simple are provided.", 750 | }, 751 | "vm_size": { 752 | "default": "Standard_DS1_V2", 753 | "description": "Specifies the size of the virtual machine.", 754 | }, 755 | "vnet_subnet_id": { 756 | "default": null, 757 | "description": "The subnet id of the virtual network where the virtual machines will reside.", 758 | }, 759 | }, 760 | } 761 | 762 | module_windowsserver_os = { 763 | "data": {}, 764 | "modules": {}, 765 | "outputs": { 766 | "calculated_remote_port": { 767 | "depends_on": [], 768 | "description": "", 769 | "sensitive": false, 770 | "value": "${element(split(\",\", lookup(var.standard_os, var.vm_os_simple, \"\")), 0) == \"MicrosoftWindowsServer\" ? 3389 : 22}", 771 | }, 772 | "calculated_value_os_offer": { 773 | "depends_on": [], 774 | "description": "", 775 | "sensitive": false, 776 | "value": "${element(split(\",\", lookup(var.standard_os, var.vm_os_simple, \"\")), 1)}", 777 | }, 778 | "calculated_value_os_publisher": { 779 | "depends_on": [], 780 | "description": "", 781 | "sensitive": false, 782 | "value": "${element(split(\",\", lookup(var.standard_os, var.vm_os_simple, \"\")), 0)}", 783 | }, 784 | "calculated_value_os_sku": { 785 | "depends_on": [], 786 | "description": "", 787 | "sensitive": false, 788 | "value": "${element(split(\",\", lookup(var.standard_os, var.vm_os_simple, \"\")), 2)}", 789 | }, 790 | }, 791 | "providers": {}, 792 | "resources": {}, 793 | "variables": { 794 | "standard_os": { 795 | "default": { 796 | "CentOS": "OpenLogic,CentOS,7.3", 797 | "CoreOS": "CoreOS,CoreOS,Stable", 798 | "Debian": "credativ,Debian,8", 799 | "RHEL": "RedHat,RHEL,7.3", 800 | "SLES": "SUSE,SLES,12-SP2", 801 | "UbuntuServer": "Canonical,UbuntuServer,16.04-LTS", 802 | "WindowsServer": "MicrosoftWindowsServer,WindowsServer,2016-Datacenter", 803 | "openSUSE-Leap": "SUSE,openSUSE-Leap,42.2", 804 | }, 805 | "description": "", 806 | }, 807 | "vm_os_simple": { 808 | "default": "", 809 | "description": "", 810 | }, 811 | }, 812 | } 813 | 814 | module_paths = [ 815 | [], 816 | [ 817 | "network", 818 | ], 819 | [ 820 | "windowsserver", 821 | ], 822 | [ 823 | "windowsserver", 824 | "os", 825 | ], 826 | ] 827 | 828 | module = func(path) { 829 | if length(path) == 0 { 830 | return _root 831 | } 832 | if length(path) == 1 and path[0] is "network" { 833 | return module_network 834 | } 835 | if length(path) == 1 and path[0] is "windowsserver" { 836 | return module_windowsserver 837 | } 838 | if length(path) == 2 and path[0] is "windowsserver" and path[1] is "os" { 839 | return module_windowsserver_os 840 | } 841 | 842 | return undefined 843 | } 844 | 845 | data = _root.data 846 | modules = _root.modules 847 | providers = _root.providers 848 | resources = _root.resources 849 | variables = _root.variables 850 | outputs = _root.outputs 851 | -------------------------------------------------------------------------------- /test/require-modules-from-pmr/tfconfig-pass.sentinel: -------------------------------------------------------------------------------- 1 | _root = { 2 | "data": {}, 3 | "modules": { 4 | "network": { 5 | "config": { 6 | "allow_ssh_traffic": "1", 7 | "location": "${var.location}", 8 | "resource_group_name": "${var.windows_dns_prefix}-rc", 9 | }, 10 | "source": "app.terraform.io/OurOrganization/network/azurerm", 11 | "version": "1.1.1", 12 | }, 13 | "windowsserver": { 14 | "config": { 15 | "admin_password": "${var.admin_password}", 16 | "location": "${var.location}", 17 | "public_ip_dns": [ 18 | "${var.windows_dns_prefix}", 19 | ], 20 | "resource_group_name": "${var.windows_dns_prefix}-rc", 21 | "vm_hostname": "demohost", 22 | "vm_os_simple": "WindowsServer", 23 | "vnet_subnet_id": "${module.network.vnet_subnets[0]}", 24 | }, 25 | "source": "app.terraform.io/OurOrganization/compute/azurerm", 26 | "version": "1.1.5", 27 | }, 28 | }, 29 | "outputs": { 30 | "windows_vm_public_name": { 31 | "depends_on": [], 32 | "description": "", 33 | "sensitive": false, 34 | "value": "${module.windowsserver.public_ip_dns_name}", 35 | }, 36 | }, 37 | "providers": {}, 38 | "resources": {}, 39 | "variables": { 40 | "admin_password": { 41 | "default": "pTFE1234!", 42 | "description": "admin password for Windows VM", 43 | }, 44 | "location": { 45 | "default": "East US", 46 | "description": "Azure location in which to create resources", 47 | }, 48 | "windows_dns_prefix": { 49 | "default": null, 50 | "description": "DNS prefix to add to to public IP address for Windows VM", 51 | }, 52 | }, 53 | } 54 | 55 | module_network = { 56 | "data": {}, 57 | "modules": {}, 58 | "outputs": { 59 | "security_group_id": { 60 | "depends_on": [], 61 | "description": "The id of the security group attached to subnets inside the newly created vNet. Use this id to associate additional network security rules to subnets.", 62 | "sensitive": false, 63 | "value": "${azurerm_network_security_group.security_group.id}", 64 | }, 65 | "vnet_address_space": { 66 | "depends_on": [], 67 | "description": "The address space of the newly created vNet", 68 | "sensitive": false, 69 | "value": "${azurerm_virtual_network.vnet.address_space}", 70 | }, 71 | "vnet_id": { 72 | "depends_on": [], 73 | "description": "The id of the newly created vNet", 74 | "sensitive": false, 75 | "value": "${azurerm_virtual_network.vnet.id}", 76 | }, 77 | "vnet_location": { 78 | "depends_on": [], 79 | "description": "The location of the newly created vNet", 80 | "sensitive": false, 81 | "value": "${azurerm_virtual_network.vnet.location}", 82 | }, 83 | "vnet_name": { 84 | "depends_on": [], 85 | "description": "The Name of the newly created vNet", 86 | "sensitive": false, 87 | "value": "${azurerm_virtual_network.vnet.name}", 88 | }, 89 | "vnet_subnets": { 90 | "depends_on": [], 91 | "description": "The ids of subnets created inside the newl vNet", 92 | "sensitive": false, 93 | "value": "${azurerm_subnet.subnet.*.id}", 94 | }, 95 | }, 96 | "providers": {}, 97 | "resources": { 98 | "azurerm_network_security_group": { 99 | "security_group": { 100 | "config": { 101 | "location": "${var.location}", 102 | "name": "${var.sg_name}", 103 | "resource_group_name": "${azurerm_resource_group.network.name}", 104 | "tags": "${var.tags}", 105 | }, 106 | "provisioners": null, 107 | }, 108 | }, 109 | "azurerm_network_security_rule": { 110 | "security_rule_rdp": { 111 | "config": { 112 | "access": "Allow", 113 | "destination_address_prefix": "*", 114 | "destination_port_range": "3389", 115 | "direction": "Inbound", 116 | "name": "rdp", 117 | "network_security_group_name": "${azurerm_network_security_group.security_group.name}", 118 | "priority": 101, 119 | "protocol": "Tcp", 120 | "resource_group_name": "${azurerm_resource_group.network.name}", 121 | "source_address_prefix": "*", 122 | "source_port_range": "*", 123 | }, 124 | "provisioners": null, 125 | }, 126 | "security_rule_ssh": { 127 | "config": { 128 | "access": "Allow", 129 | "destination_address_prefix": "*", 130 | "destination_port_range": "22", 131 | "direction": "Inbound", 132 | "name": "ssh", 133 | "network_security_group_name": "${azurerm_network_security_group.security_group.name}", 134 | "priority": 102, 135 | "protocol": "Tcp", 136 | "resource_group_name": "${azurerm_resource_group.network.name}", 137 | "source_address_prefix": "*", 138 | "source_port_range": "*", 139 | }, 140 | "provisioners": null, 141 | }, 142 | }, 143 | "azurerm_resource_group": { 144 | "network": { 145 | "config": { 146 | "location": "${var.location}", 147 | "name": "${var.resource_group_name}", 148 | }, 149 | "provisioners": null, 150 | }, 151 | }, 152 | "azurerm_subnet": { 153 | "subnet": { 154 | "config": { 155 | "address_prefix": "${var.subnet_prefixes[count.index]}", 156 | "name": "${var.subnet_names[count.index]}", 157 | "network_security_group_id": "${azurerm_network_security_group.security_group.id}", 158 | "resource_group_name": "${azurerm_resource_group.network.name}", 159 | "virtual_network_name": "${azurerm_virtual_network.vnet.name}", 160 | }, 161 | "provisioners": null, 162 | }, 163 | }, 164 | "azurerm_virtual_network": { 165 | "vnet": { 166 | "config": { 167 | "address_space": [ 168 | "${var.address_space}", 169 | ], 170 | "dns_servers": "${var.dns_servers}", 171 | "location": "${var.location}", 172 | "name": "${var.vnet_name}", 173 | "resource_group_name": "${azurerm_resource_group.network.name}", 174 | "tags": "${var.tags}", 175 | }, 176 | "provisioners": null, 177 | }, 178 | }, 179 | }, 180 | "variables": { 181 | "address_space": { 182 | "default": "10.0.0.0/16", 183 | "description": "The address space that is used by the virtual network.", 184 | }, 185 | "allow_rdp_traffic": { 186 | "default": "0", 187 | "description": "This optional variable, when set to true, adds a security rule allowing RDP traffic to flow through to the newly created network. The default value is false.", 188 | }, 189 | "allow_ssh_traffic": { 190 | "default": "0", 191 | "description": "This optional variable, when set to true, adds a security rule allowing SSH traffic to flow through to the newly created network. The default value is false.", 192 | }, 193 | "dns_servers": { 194 | "default": [], 195 | "description": "The DNS servers to be used with vNet.", 196 | }, 197 | "location": { 198 | "default": null, 199 | "description": "The location/region where the core network will be created. The full list of Azure regions can be found at https://azure.microsoft.com/regions", 200 | }, 201 | "resource_group_name": { 202 | "default": "myapp-rg", 203 | "description": "Default resource group name that the network will be created in.", 204 | }, 205 | "sg_name": { 206 | "default": "acctsecgrp", 207 | "description": "Give a name to security group", 208 | }, 209 | "subnet_names": { 210 | "default": [ 211 | "subnet1", 212 | ], 213 | "description": "A list of public subnets inside the vNet.", 214 | }, 215 | "subnet_prefixes": { 216 | "default": [ 217 | "10.0.1.0/24", 218 | ], 219 | "description": "The address prefix to use for the subnet.", 220 | }, 221 | "tags": { 222 | "default": { 223 | "tag1": "", 224 | "tag2": "", 225 | }, 226 | "description": "The tags to associate with your network and subnets.", 227 | }, 228 | "vnet_name": { 229 | "default": "acctvnet", 230 | "description": "Name of the vnet to create", 231 | }, 232 | }, 233 | } 234 | 235 | module_windowsserver = { 236 | "data": {}, 237 | "modules": { 238 | "os": { 239 | "config": { 240 | "vm_os_simple": "${var.vm_os_simple}", 241 | }, 242 | "source": "./os", 243 | "version": "", 244 | }, 245 | }, 246 | "outputs": { 247 | "availability_set_id": { 248 | "depends_on": [], 249 | "description": "id of the availability set where the vms are provisioned.", 250 | "sensitive": false, 251 | "value": "${azurerm_availability_set.vm.id}", 252 | }, 253 | "network_interface_ids": { 254 | "depends_on": [], 255 | "description": "ids of the vm nics provisoned.", 256 | "sensitive": false, 257 | "value": "${azurerm_network_interface.vm.*.id}", 258 | }, 259 | "network_interface_private_ip": { 260 | "depends_on": [], 261 | "description": "private ip addresses of the vm nics", 262 | "sensitive": false, 263 | "value": "${azurerm_network_interface.vm.*.private_ip_address}", 264 | }, 265 | "network_security_group_id": { 266 | "depends_on": [], 267 | "description": "id of the security group provisioned", 268 | "sensitive": false, 269 | "value": "${azurerm_network_security_group.vm.id}", 270 | }, 271 | "public_ip_address": { 272 | "depends_on": [], 273 | "description": "The actual ip address allocated for the resource.", 274 | "sensitive": false, 275 | "value": "${azurerm_public_ip.vm.*.ip_address}", 276 | }, 277 | "public_ip_dns_name": { 278 | "depends_on": [], 279 | "description": "fqdn to connect to the first vm provisioned.", 280 | "sensitive": false, 281 | "value": "${azurerm_public_ip.vm.*.fqdn}", 282 | }, 283 | "public_ip_id": { 284 | "depends_on": [], 285 | "description": "id of the public ip address provisoned.", 286 | "sensitive": false, 287 | "value": "${azurerm_public_ip.vm.*.id}", 288 | }, 289 | "vm_ids": { 290 | "depends_on": [], 291 | "description": "Virtual machine ids created.", 292 | "sensitive": false, 293 | "value": "${concat(azurerm_virtual_machine.vm-windows.*.id, azurerm_virtual_machine.vm-linux.*.id)}", 294 | }, 295 | }, 296 | "providers": { 297 | "azurerm": { 298 | "alias": {}, 299 | "config": {}, 300 | "version": "~> 0.3", 301 | }, 302 | "random": { 303 | "alias": {}, 304 | "config": {}, 305 | "version": "~> 1.0", 306 | }, 307 | }, 308 | "resources": { 309 | "azurerm_availability_set": { 310 | "vm": { 311 | "config": { 312 | "location": "${azurerm_resource_group.vm.location}", 313 | "managed": true, 314 | "name": "${var.vm_hostname}-avset", 315 | "platform_fault_domain_count": 2, 316 | "platform_update_domain_count": 2, 317 | "resource_group_name": "${azurerm_resource_group.vm.name}", 318 | }, 319 | "provisioners": null, 320 | }, 321 | }, 322 | "azurerm_network_interface": { 323 | "vm": { 324 | "config": { 325 | "ip_configuration": [ 326 | { 327 | "name": "ipconfig${count.index}", 328 | "private_ip_address_allocation": "Dynamic", 329 | "public_ip_address_id": "${length(azurerm_public_ip.vm.*.id) > 0 ? element(concat(azurerm_public_ip.vm.*.id, list(\"\")), count.index) : \"\"}", 330 | "subnet_id": "${var.vnet_subnet_id}", 331 | }, 332 | ], 333 | "location": "${azurerm_resource_group.vm.location}", 334 | "name": "nic-${var.vm_hostname}-${count.index}", 335 | "network_security_group_id": "${azurerm_network_security_group.vm.id}", 336 | "resource_group_name": "${azurerm_resource_group.vm.name}", 337 | }, 338 | "provisioners": null, 339 | }, 340 | }, 341 | "azurerm_network_security_group": { 342 | "vm": { 343 | "config": { 344 | "location": "${azurerm_resource_group.vm.location}", 345 | "name": "${var.vm_hostname}-${coalesce(var.remote_port,module.os.calculated_remote_port)}-nsg", 346 | "resource_group_name": "${azurerm_resource_group.vm.name}", 347 | "security_rule": [ 348 | { 349 | "access": "Allow", 350 | "description": "Allow remote protocol in from all locations", 351 | "destination_address_prefix": "*", 352 | "destination_port_range": "${coalesce(var.remote_port,module.os.calculated_remote_port)}", 353 | "direction": "Inbound", 354 | "name": "allow_remote_${coalesce(var.remote_port,module.os.calculated_remote_port)}_in_all", 355 | "priority": 100, 356 | "protocol": "Tcp", 357 | "source_address_prefix": "*", 358 | "source_port_range": "*", 359 | }, 360 | ], 361 | }, 362 | "provisioners": null, 363 | }, 364 | }, 365 | "azurerm_public_ip": { 366 | "vm": { 367 | "config": { 368 | "domain_name_label": "${element(var.public_ip_dns, count.index)}", 369 | "location": "${var.location}", 370 | "name": "${var.vm_hostname}-${count.index}-publicIP", 371 | "public_ip_address_allocation": "${var.public_ip_address_allocation}", 372 | "resource_group_name": "${azurerm_resource_group.vm.name}", 373 | }, 374 | "provisioners": null, 375 | }, 376 | }, 377 | "azurerm_resource_group": { 378 | "vm": { 379 | "config": { 380 | "location": "${var.location}", 381 | "name": "${var.resource_group_name}", 382 | "tags": "${var.tags}", 383 | }, 384 | "provisioners": null, 385 | }, 386 | }, 387 | "azurerm_storage_account": { 388 | "vm-sa": { 389 | "config": { 390 | "account_replication_type": "${element(split(\"_\", var.boot_diagnostics_sa_type),1)}", 391 | "account_tier": "${element(split(\"_\", var.boot_diagnostics_sa_type),0)}", 392 | "location": "${var.location}", 393 | "name": "bootdiag${lower(random_id.vm-sa.hex)}", 394 | "resource_group_name": "${azurerm_resource_group.vm.name}", 395 | "tags": "${var.tags}", 396 | }, 397 | "provisioners": null, 398 | }, 399 | }, 400 | "azurerm_virtual_machine": { 401 | "vm-linux": { 402 | "config": { 403 | "availability_set_id": "${azurerm_availability_set.vm.id}", 404 | "boot_diagnostics": [ 405 | { 406 | "enabled": "${var.boot_diagnostics}", 407 | "storage_uri": "${var.boot_diagnostics == \"true\" ? join(\",\", azurerm_storage_account.vm-sa.*.primary_blob_endpoint) : \"\" }", 408 | }, 409 | ], 410 | "delete_os_disk_on_termination": "${var.delete_os_disk_on_termination}", 411 | "location": "${var.location}", 412 | "name": "${var.vm_hostname}${count.index}", 413 | "network_interface_ids": [ 414 | "${element(azurerm_network_interface.vm.*.id, count.index)}", 415 | ], 416 | "os_profile": [ 417 | { 418 | "admin_password": "${var.admin_password}", 419 | "admin_username": "${var.admin_username}", 420 | "computer_name": "${var.vm_hostname}${count.index}", 421 | }, 422 | ], 423 | "os_profile_linux_config": [ 424 | { 425 | "disable_password_authentication": true, 426 | "ssh_keys": [ 427 | { 428 | "key_data": "${file(\"${var.ssh_key}\")}", 429 | "path": "/home/${var.admin_username}/.ssh/authorized_keys", 430 | }, 431 | ], 432 | }, 433 | ], 434 | "resource_group_name": "${azurerm_resource_group.vm.name}", 435 | "storage_image_reference": [ 436 | { 437 | "id": "${var.vm_os_id}", 438 | "offer": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_offer, module.os.calculated_value_os_offer) : \"\"}", 439 | "publisher": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_publisher, module.os.calculated_value_os_publisher) : \"\"}", 440 | "sku": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_sku, module.os.calculated_value_os_sku) : \"\"}", 441 | "version": "${var.vm_os_id == \"\" ? var.vm_os_version : \"\"}", 442 | }, 443 | ], 444 | "storage_os_disk": [ 445 | { 446 | "caching": "ReadWrite", 447 | "create_option": "FromImage", 448 | "managed_disk_type": "${var.storage_account_type}", 449 | "name": "osdisk-${var.vm_hostname}-${count.index}", 450 | }, 451 | ], 452 | "tags": "${var.tags}", 453 | "vm_size": "${var.vm_size}", 454 | }, 455 | "provisioners": null, 456 | }, 457 | "vm-linux-with-datadisk": { 458 | "config": { 459 | "availability_set_id": "${azurerm_availability_set.vm.id}", 460 | "boot_diagnostics": [ 461 | { 462 | "enabled": "${var.boot_diagnostics}", 463 | "storage_uri": "${var.boot_diagnostics == \"true\" ? join(\",\", azurerm_storage_account.vm-sa.*.primary_blob_endpoint) : \"\" }", 464 | }, 465 | ], 466 | "delete_os_disk_on_termination": "${var.delete_os_disk_on_termination}", 467 | "location": "${var.location}", 468 | "name": "${var.vm_hostname}${count.index}", 469 | "network_interface_ids": [ 470 | "${element(azurerm_network_interface.vm.*.id, count.index)}", 471 | ], 472 | "os_profile": [ 473 | { 474 | "admin_password": "${var.admin_password}", 475 | "admin_username": "${var.admin_username}", 476 | "computer_name": "${var.vm_hostname}${count.index}", 477 | }, 478 | ], 479 | "os_profile_linux_config": [ 480 | { 481 | "disable_password_authentication": true, 482 | "ssh_keys": [ 483 | { 484 | "key_data": "${file(\"${var.ssh_key}\")}", 485 | "path": "/home/${var.admin_username}/.ssh/authorized_keys", 486 | }, 487 | ], 488 | }, 489 | ], 490 | "resource_group_name": "${azurerm_resource_group.vm.name}", 491 | "storage_data_disk": [ 492 | { 493 | "create_option": "Empty", 494 | "disk_size_gb": "${var.data_disk_size_gb}", 495 | "lun": 0, 496 | "managed_disk_type": "${var.data_sa_type}", 497 | "name": "datadisk-${var.vm_hostname}-${count.index}", 498 | }, 499 | ], 500 | "storage_image_reference": [ 501 | { 502 | "id": "${var.vm_os_id}", 503 | "offer": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_offer, module.os.calculated_value_os_offer) : \"\"}", 504 | "publisher": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_publisher, module.os.calculated_value_os_publisher) : \"\"}", 505 | "sku": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_sku, module.os.calculated_value_os_sku) : \"\"}", 506 | "version": "${var.vm_os_id == \"\" ? var.vm_os_version : \"\"}", 507 | }, 508 | ], 509 | "storage_os_disk": [ 510 | { 511 | "caching": "ReadWrite", 512 | "create_option": "FromImage", 513 | "managed_disk_type": "${var.storage_account_type}", 514 | "name": "osdisk-${var.vm_hostname}-${count.index}", 515 | }, 516 | ], 517 | "tags": "${var.tags}", 518 | "vm_size": "${var.vm_size}", 519 | }, 520 | "provisioners": null, 521 | }, 522 | "vm-windows": { 523 | "config": { 524 | "availability_set_id": "${azurerm_availability_set.vm.id}", 525 | "boot_diagnostics": [ 526 | { 527 | "enabled": "${var.boot_diagnostics}", 528 | "storage_uri": "${var.boot_diagnostics == \"true\" ? join(\",\", azurerm_storage_account.vm-sa.*.primary_blob_endpoint) : \"\" }", 529 | }, 530 | ], 531 | "delete_os_disk_on_termination": "${var.delete_os_disk_on_termination}", 532 | "location": "${var.location}", 533 | "name": "${var.vm_hostname}${count.index}", 534 | "network_interface_ids": [ 535 | "${element(azurerm_network_interface.vm.*.id, count.index)}", 536 | ], 537 | "os_profile": [ 538 | { 539 | "admin_password": "${var.admin_password}", 540 | "admin_username": "${var.admin_username}", 541 | "computer_name": "${var.vm_hostname}${count.index}", 542 | }, 543 | ], 544 | "os_profile_windows_config": [ 545 | {}, 546 | ], 547 | "resource_group_name": "${azurerm_resource_group.vm.name}", 548 | "storage_image_reference": [ 549 | { 550 | "id": "${var.vm_os_id}", 551 | "offer": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_offer, module.os.calculated_value_os_offer) : \"\"}", 552 | "publisher": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_publisher, module.os.calculated_value_os_publisher) : \"\"}", 553 | "sku": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_sku, module.os.calculated_value_os_sku) : \"\"}", 554 | "version": "${var.vm_os_id == \"\" ? var.vm_os_version : \"\"}", 555 | }, 556 | ], 557 | "storage_os_disk": [ 558 | { 559 | "caching": "ReadWrite", 560 | "create_option": "FromImage", 561 | "managed_disk_type": "${var.storage_account_type}", 562 | "name": "osdisk-${var.vm_hostname}-${count.index}", 563 | }, 564 | ], 565 | "tags": "${var.tags}", 566 | "vm_size": "${var.vm_size}", 567 | }, 568 | "provisioners": null, 569 | }, 570 | "vm-windows-with-datadisk": { 571 | "config": { 572 | "availability_set_id": "${azurerm_availability_set.vm.id}", 573 | "boot_diagnostics": [ 574 | { 575 | "enabled": "${var.boot_diagnostics}", 576 | "storage_uri": "${var.boot_diagnostics == \"true\" ? join(\",\", azurerm_storage_account.vm-sa.*.primary_blob_endpoint) : \"\" }", 577 | }, 578 | ], 579 | "delete_os_disk_on_termination": "${var.delete_os_disk_on_termination}", 580 | "location": "${var.location}", 581 | "name": "${var.vm_hostname}${count.index}", 582 | "network_interface_ids": [ 583 | "${element(azurerm_network_interface.vm.*.id, count.index)}", 584 | ], 585 | "os_profile": [ 586 | { 587 | "admin_password": "${var.admin_password}", 588 | "admin_username": "${var.admin_username}", 589 | "computer_name": "${var.vm_hostname}${count.index}", 590 | }, 591 | ], 592 | "os_profile_windows_config": [ 593 | {}, 594 | ], 595 | "resource_group_name": "${azurerm_resource_group.vm.name}", 596 | "storage_data_disk": [ 597 | { 598 | "create_option": "Empty", 599 | "disk_size_gb": "${var.data_disk_size_gb}", 600 | "lun": 0, 601 | "managed_disk_type": "${var.data_sa_type}", 602 | "name": "datadisk-${var.vm_hostname}-${count.index}", 603 | }, 604 | ], 605 | "storage_image_reference": [ 606 | { 607 | "id": "${var.vm_os_id}", 608 | "offer": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_offer, module.os.calculated_value_os_offer) : \"\"}", 609 | "publisher": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_publisher, module.os.calculated_value_os_publisher) : \"\"}", 610 | "sku": "${var.vm_os_id == \"\" ? coalesce(var.vm_os_sku, module.os.calculated_value_os_sku) : \"\"}", 611 | "version": "${var.vm_os_id == \"\" ? var.vm_os_version : \"\"}", 612 | }, 613 | ], 614 | "storage_os_disk": [ 615 | { 616 | "caching": "ReadWrite", 617 | "create_option": "FromImage", 618 | "managed_disk_type": "${var.storage_account_type}", 619 | "name": "osdisk-${var.vm_hostname}-${count.index}", 620 | }, 621 | ], 622 | "tags": "${var.tags}", 623 | "vm_size": "${var.vm_size}", 624 | }, 625 | "provisioners": null, 626 | }, 627 | }, 628 | "random_id": { 629 | "vm-sa": { 630 | "config": { 631 | "byte_length": 6, 632 | "keepers": [ 633 | { 634 | "vm_hostname": "${var.vm_hostname}", 635 | }, 636 | ], 637 | }, 638 | "provisioners": null, 639 | }, 640 | }, 641 | }, 642 | "variables": { 643 | "admin_password": { 644 | "default": "", 645 | "description": "The admin password to be used on the VMSS that will be deployed. The password must meet the complexity requirements of Azure", 646 | }, 647 | "admin_username": { 648 | "default": "azureuser", 649 | "description": "The admin username of the VM that will be deployed", 650 | }, 651 | "boot_diagnostics": { 652 | "default": "false", 653 | "description": "(Optional) Enable or Disable boot diagnostics", 654 | }, 655 | "boot_diagnostics_sa_type": { 656 | "default": "Standard_LRS", 657 | "description": "(Optional) Storage account type for boot diagnostics", 658 | }, 659 | "data_disk": { 660 | "default": "false", 661 | "description": "Set to true to add a datadisk.", 662 | }, 663 | "data_disk_size_gb": { 664 | "default": "", 665 | "description": "Storage data disk size size", 666 | }, 667 | "data_sa_type": { 668 | "default": "Standard_LRS", 669 | "description": "Data Disk Storage Account type", 670 | }, 671 | "delete_os_disk_on_termination": { 672 | "default": "false", 673 | "description": "Delete datadisk when machine is terminated", 674 | }, 675 | "is_windows_image": { 676 | "default": "false", 677 | "description": "Boolean flag to notify when the custom image is windows based. Only used in conjunction with vm_os_id", 678 | }, 679 | "location": { 680 | "default": null, 681 | "description": "The location/region where the virtual network is created. Changing this forces a new resource to be created.", 682 | }, 683 | "nb_instances": { 684 | "default": "1", 685 | "description": "Specify the number of vm instances", 686 | }, 687 | "nb_public_ip": { 688 | "default": "1", 689 | "description": "Number of public IPs to assign corresponding to one IP per vm. Set to 0 to not assign any public IP addresses.", 690 | }, 691 | "public_ip_address_allocation": { 692 | "default": "dynamic", 693 | "description": "Defines how an IP address is assigned. Options are Static or Dynamic.", 694 | }, 695 | "public_ip_dns": { 696 | "default": [ 697 | "", 698 | ], 699 | "description": "Optional globally unique per datacenter region domain name label to apply to each public ip address. e.g. thisvar.varlocation.cloudapp.azure.com where you specify only thisvar here. This is an array of names which will pair up sequentially to the number of public ips defined in var.nb_public_ip. One name or empty string is required for every public ip. If no public ip is desired, then set this to an array with a single empty string.", 700 | }, 701 | "remote_port": { 702 | "default": "", 703 | "description": "Remote tcp port to be used for access to the vms created via the nsg applied to the nics.", 704 | }, 705 | "resource_group_name": { 706 | "default": "terraform-compute", 707 | "description": "The name of the resource group in which the resources will be created", 708 | }, 709 | "ssh_key": { 710 | "default": "~/.ssh/id_rsa.pub", 711 | "description": "Path to the public key to be used for ssh access to the VM. Only used with non-Windows vms and can be left as-is even if using Windows vms. If specifying a path to a certification on a Windows machine to provision a linux vm use the / in the path versus backslash. e.g. c:/home/id_rsa.pub", 712 | }, 713 | "storage_account_type": { 714 | "default": "Premium_LRS", 715 | "description": "Defines the type of storage account to be created. Valid options are Standard_LRS, Standard_ZRS, Standard_GRS, Standard_RAGRS, Premium_LRS.", 716 | }, 717 | "tags": { 718 | "default": { 719 | "source": "terraform", 720 | }, 721 | "description": "A map of the tags to use on the resources that are deployed with this module.", 722 | }, 723 | "vm_hostname": { 724 | "default": "myvm", 725 | "description": "local name of the VM", 726 | }, 727 | "vm_os_id": { 728 | "default": "", 729 | "description": "The resource ID of the image that you want to deploy if you are using a custom image.Note, need to provide is_windows_image = true for windows custom images.", 730 | }, 731 | "vm_os_offer": { 732 | "default": "", 733 | "description": "The name of the offer of the image that you want to deploy. This is ignored when vm_os_id or vm_os_simple are provided.", 734 | }, 735 | "vm_os_publisher": { 736 | "default": "", 737 | "description": "The name of the publisher of the image that you want to deploy. This is ignored when vm_os_id or vm_os_simple are provided.", 738 | }, 739 | "vm_os_simple": { 740 | "default": "", 741 | "description": "Specify UbuntuServer, WindowsServer, RHEL, openSUSE-Leap, CentOS, Debian, CoreOS and SLES to get the latest image version of the specified os. Do not provide this value if a custom value is used for vm_os_publisher, vm_os_offer, and vm_os_sku.", 742 | }, 743 | "vm_os_sku": { 744 | "default": "", 745 | "description": "The sku of the image that you want to deploy. This is ignored when vm_os_id or vm_os_simple are provided.", 746 | }, 747 | "vm_os_version": { 748 | "default": "latest", 749 | "description": "The version of the image that you want to deploy. This is ignored when vm_os_id or vm_os_simple are provided.", 750 | }, 751 | "vm_size": { 752 | "default": "Standard_DS1_V2", 753 | "description": "Specifies the size of the virtual machine.", 754 | }, 755 | "vnet_subnet_id": { 756 | "default": null, 757 | "description": "The subnet id of the virtual network where the virtual machines will reside.", 758 | }, 759 | }, 760 | } 761 | 762 | module_windowsserver_os = { 763 | "data": {}, 764 | "modules": {}, 765 | "outputs": { 766 | "calculated_remote_port": { 767 | "depends_on": [], 768 | "description": "", 769 | "sensitive": false, 770 | "value": "${element(split(\",\", lookup(var.standard_os, var.vm_os_simple, \"\")), 0) == \"MicrosoftWindowsServer\" ? 3389 : 22}", 771 | }, 772 | "calculated_value_os_offer": { 773 | "depends_on": [], 774 | "description": "", 775 | "sensitive": false, 776 | "value": "${element(split(\",\", lookup(var.standard_os, var.vm_os_simple, \"\")), 1)}", 777 | }, 778 | "calculated_value_os_publisher": { 779 | "depends_on": [], 780 | "description": "", 781 | "sensitive": false, 782 | "value": "${element(split(\",\", lookup(var.standard_os, var.vm_os_simple, \"\")), 0)}", 783 | }, 784 | "calculated_value_os_sku": { 785 | "depends_on": [], 786 | "description": "", 787 | "sensitive": false, 788 | "value": "${element(split(\",\", lookup(var.standard_os, var.vm_os_simple, \"\")), 2)}", 789 | }, 790 | }, 791 | "providers": {}, 792 | "resources": {}, 793 | "variables": { 794 | "standard_os": { 795 | "default": { 796 | "CentOS": "OpenLogic,CentOS,7.3", 797 | "CoreOS": "CoreOS,CoreOS,Stable", 798 | "Debian": "credativ,Debian,8", 799 | "RHEL": "RedHat,RHEL,7.3", 800 | "SLES": "SUSE,SLES,12-SP2", 801 | "UbuntuServer": "Canonical,UbuntuServer,16.04-LTS", 802 | "WindowsServer": "MicrosoftWindowsServer,WindowsServer,2016-Datacenter", 803 | "openSUSE-Leap": "SUSE,openSUSE-Leap,42.2", 804 | }, 805 | "description": "", 806 | }, 807 | "vm_os_simple": { 808 | "default": "", 809 | "description": "", 810 | }, 811 | }, 812 | } 813 | 814 | module_paths = [ 815 | [], 816 | [ 817 | "network", 818 | ], 819 | [ 820 | "windowsserver", 821 | ], 822 | [ 823 | "windowsserver", 824 | "os", 825 | ], 826 | ] 827 | 828 | module = func(path) { 829 | if length(path) == 0 { 830 | return _root 831 | } 832 | if length(path) == 1 and path[0] is "network" { 833 | return module_network 834 | } 835 | if length(path) == 1 and path[0] is "windowsserver" { 836 | return module_windowsserver 837 | } 838 | if length(path) == 2 and path[0] is "windowsserver" and path[1] is "os" { 839 | return module_windowsserver_os 840 | } 841 | 842 | return undefined 843 | } 844 | 845 | data = _root.data 846 | modules = _root.modules 847 | providers = _root.providers 848 | resources = _root.resources 849 | variables = _root.variables 850 | outputs = _root.outputs 851 | -------------------------------------------------------------------------------- /test/tfe_policies_only/fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": { 3 | "main": false 4 | }, 5 | "mock": { 6 | "tfplan": { 7 | "resources": { 8 | "tfe_team_access": { 9 | "foo": { 10 | "0": { 11 | "applied": { 12 | "access": "admin", 13 | "team_id": "team-wrongteam", 14 | "workspace_id": "ws-wrongworkspace" 15 | } 16 | } 17 | } 18 | }, 19 | "tfe_sentinel_policy": { 20 | "passthrough": { 21 | "0": { 22 | "applied": { 23 | "name": "passthrough", 24 | "description": "returns true", 25 | "organization": "example_corp", 26 | "policy": "import \"tfplan\"...", 27 | "enforce_mode": "hard-mandatory" 28 | } 29 | } 30 | } 31 | }, 32 | "tfe_policy_set": { 33 | "global": { 34 | "0": { 35 | "applied": { 36 | "name": "global", 37 | "description": "All infrastructure", 38 | "organization": "example_corp", 39 | "global": true, 40 | "policy_ids": ["pol-43123passthrough"] 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/tfe_policies_only/pass.json: -------------------------------------------------------------------------------- 1 | { 2 | "test": { 3 | "main": true 4 | }, 5 | "mock": { 6 | "tfplan": { 7 | "resources": { 8 | "tfe_sentinel_policy": { 9 | "passthrough": { 10 | "0": { 11 | "applied": { 12 | "name": "passthrough", 13 | "description": "returns true", 14 | "organization": "example_corp", 15 | "policy": "import \"tfplan\"...", 16 | "enforce_mode": "hard-mandatory" 17 | } 18 | } 19 | } 20 | }, 21 | "tfe_policy_set": { 22 | "global": { 23 | "0": { 24 | "applied": { 25 | "name": "global", 26 | "description": "All infrastructure", 27 | "organization": "example_corp", 28 | "global": true, 29 | "policy_ids": ["pol-43123passthrough"] 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tfe_policies_only.sentinel: -------------------------------------------------------------------------------- 1 | import "tfplan" 2 | 3 | # Get an array of all resources of the given type (or an empty array). 4 | get_resources = func(type) { 5 | if length(tfplan.module_paths else []) > 0 { # always true in the real tfplan import 6 | return get_resources_all_modules(type) 7 | } else { # fallback for tests 8 | return get_resources_root_only(type) 9 | } 10 | } 11 | 12 | get_resources_root_only = func(type) { 13 | resources = [] 14 | named_and_counted_resources = tfplan.resources[type] else {} 15 | # Get resource bodies out of nested resource maps, from: 16 | # {"name": {"0": {"applied": {...}, "diff": {...} }, "1": {...}}, "name": {...}} 17 | # to: 18 | # [{"applied": {...}, "diff": {...}}, {"applied": {...}, "diff": {...}}, ...] 19 | for named_and_counted_resources as _, instances { 20 | for instances as _, body { 21 | append(resources, body) 22 | } 23 | } 24 | return resources 25 | } 26 | 27 | get_resources_all_modules = func(type) { 28 | resources = [] 29 | for tfplan.module_paths as path { 30 | named_and_counted_resources = tfplan.module(path).resources[type] else {} 31 | # Get resource bodies out of nested resource maps, from: 32 | # {"name": {"0": {"applied": {...}, "diff": {...} }, "1": {...}}, "name": {...}} 33 | # to: 34 | # [{"applied": {...}, "diff": {...}}, {"applied": {...}, "diff": {...}}, ...] 35 | for named_and_counted_resources as _, instances { 36 | for instances as _, body { 37 | append(resources, body) 38 | } 39 | } 40 | } 41 | return resources 42 | } 43 | 44 | no_tfe_oauth_client = rule { length(get_resources("tfe_oauth_client")) == 0 } 45 | no_tfe_organization = rule { length(get_resources("tfe_organization")) == 0 } 46 | no_tfe_organization_token = rule { length(get_resources("tfe_organization_token")) == 0 } 47 | no_tfe_ssh_key = rule { length(get_resources("tfe_ssh_key")) == 0 } 48 | no_tfe_team = rule { length(get_resources("tfe_team")) == 0 } 49 | no_tfe_team_access = rule { length(get_resources("tfe_team_access")) == 0 } 50 | no_tfe_team_member = rule { length(get_resources("tfe_team_member")) == 0 } 51 | no_tfe_team_members = rule { length(get_resources("tfe_team_members")) == 0 } 52 | no_tfe_team_token = rule { length(get_resources("tfe_team_token")) == 0 } 53 | no_tfe_variable = rule { length(get_resources("tfe_variable")) == 0 } 54 | no_tfe_workspace = rule { length(get_resources("tfe_workspace")) == 0 } 55 | 56 | main = rule { 57 | no_tfe_oauth_client and 58 | no_tfe_organization and 59 | no_tfe_organization_token and 60 | no_tfe_ssh_key and 61 | no_tfe_team and 62 | no_tfe_team_access and 63 | no_tfe_team_member and 64 | no_tfe_team_members and 65 | no_tfe_team_token and 66 | no_tfe_variable and 67 | no_tfe_workspace 68 | } --------------------------------------------------------------------------------