├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .rubocop.yml ├── .ruby-version ├── .terraform-version ├── Gemfile ├── Gemfile.lock ├── LICENCE ├── README.md ├── Rakefile ├── jenkins.sh ├── lib └── tasks │ ├── generate_terraform.rake │ ├── terraform.rake │ ├── utilities │ ├── common.rb │ ├── generate.rb │ └── zone_file_field_validator.rb │ └── validate_yaml.rake └── spec ├── common_spec.rb ├── generate_spec.rb ├── spec_helper.rb ├── validate_published_dns_spec.rb └── validate_yaml_spec.rb /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - uses: ruby/setup-ruby@v1 9 | 10 | - name: Set up Homebrew 11 | id: set-up-homebrew 12 | uses: Homebrew/actions/setup-homebrew@master 13 | - name: Install Terraform 14 | run: | 15 | brew install tfenv 16 | tfenv install 17 | - run: bundle install --jobs 4 --retry 3 --deployment 18 | - run: bundle exec rake 19 | 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .terraform/ 2 | *.tfstate 3 | *.tfstate.backup 4 | tf-tmp/ 5 | graph.png 6 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_gem: 2 | rubocop-govuk: 3 | - config/default.yml 4 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.2.0 2 | -------------------------------------------------------------------------------- /.terraform-version: -------------------------------------------------------------------------------- 1 | 1.4.1 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "awspec", "~> 1.0.0" 4 | gem "dnsruby" 5 | 6 | gem "minitest", "~> 5.0" 7 | gem "rake", "~> 12.3" 8 | gem "rspec" 9 | gem "rubocop-govuk" 10 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activesupport (7.0.4.3) 5 | concurrent-ruby (~> 1.0, >= 1.0.2) 6 | i18n (>= 1.6, < 2) 7 | minitest (>= 5.1) 8 | tzinfo (~> 2.0) 9 | ast (2.4.2) 10 | aws-eventstream (1.2.0) 11 | aws-partitions (1.605.0) 12 | aws-sdk (3.1.0) 13 | aws-sdk-resources (~> 3) 14 | aws-sdk-accessanalyzer (1.29.0) 15 | aws-sdk-core (~> 3, >= 3.127.0) 16 | aws-sigv4 (~> 1.1) 17 | aws-sdk-account (1.6.0) 18 | aws-sdk-core (~> 3, >= 3.127.0) 19 | aws-sigv4 (~> 1.1) 20 | aws-sdk-acm (1.51.0) 21 | aws-sdk-core (~> 3, >= 3.127.0) 22 | aws-sigv4 (~> 1.1) 23 | aws-sdk-acmpca (1.48.0) 24 | aws-sdk-core (~> 3, >= 3.127.0) 25 | aws-sigv4 (~> 1.1) 26 | aws-sdk-alexaforbusiness (1.56.0) 27 | aws-sdk-core (~> 3, >= 3.127.0) 28 | aws-sigv4 (~> 1.1) 29 | aws-sdk-amplify (1.41.0) 30 | aws-sdk-core (~> 3, >= 3.127.0) 31 | aws-sigv4 (~> 1.1) 32 | aws-sdk-amplifybackend (1.17.0) 33 | aws-sdk-core (~> 3, >= 3.127.0) 34 | aws-sigv4 (~> 1.1) 35 | aws-sdk-amplifyuibuilder (1.5.0) 36 | aws-sdk-core (~> 3, >= 3.127.0) 37 | aws-sigv4 (~> 1.1) 38 | aws-sdk-apigateway (1.78.0) 39 | aws-sdk-core (~> 3, >= 3.127.0) 40 | aws-sigv4 (~> 1.1) 41 | aws-sdk-apigatewaymanagementapi (1.30.0) 42 | aws-sdk-core (~> 3, >= 3.127.0) 43 | aws-sigv4 (~> 1.1) 44 | aws-sdk-apigatewayv2 (1.42.0) 45 | aws-sdk-core (~> 3, >= 3.127.0) 46 | aws-sigv4 (~> 1.1) 47 | aws-sdk-appconfig (1.25.0) 48 | aws-sdk-core (~> 3, >= 3.127.0) 49 | aws-sigv4 (~> 1.1) 50 | aws-sdk-appconfigdata (1.5.0) 51 | aws-sdk-core (~> 3, >= 3.127.0) 52 | aws-sigv4 (~> 1.1) 53 | aws-sdk-appflow (1.27.0) 54 | aws-sdk-core (~> 3, >= 3.127.0) 55 | aws-sigv4 (~> 1.1) 56 | aws-sdk-appintegrationsservice (1.13.0) 57 | aws-sdk-core (~> 3, >= 3.127.0) 58 | aws-sigv4 (~> 1.1) 59 | aws-sdk-applicationautoscaling (1.62.0) 60 | aws-sdk-core (~> 3, >= 3.127.0) 61 | aws-sigv4 (~> 1.1) 62 | aws-sdk-applicationcostprofiler (1.9.0) 63 | aws-sdk-core (~> 3, >= 3.127.0) 64 | aws-sigv4 (~> 1.1) 65 | aws-sdk-applicationdiscoveryservice (1.45.0) 66 | aws-sdk-core (~> 3, >= 3.127.0) 67 | aws-sigv4 (~> 1.1) 68 | aws-sdk-applicationinsights (1.31.0) 69 | aws-sdk-core (~> 3, >= 3.127.0) 70 | aws-sigv4 (~> 1.1) 71 | aws-sdk-appmesh (1.46.0) 72 | aws-sdk-core (~> 3, >= 3.127.0) 73 | aws-sigv4 (~> 1.1) 74 | aws-sdk-appregistry (1.16.0) 75 | aws-sdk-core (~> 3, >= 3.127.0) 76 | aws-sigv4 (~> 1.1) 77 | aws-sdk-apprunner (1.14.0) 78 | aws-sdk-core (~> 3, >= 3.127.0) 79 | aws-sigv4 (~> 1.1) 80 | aws-sdk-appstream (1.66.0) 81 | aws-sdk-core (~> 3, >= 3.127.0) 82 | aws-sigv4 (~> 1.1) 83 | aws-sdk-appsync (1.52.0) 84 | aws-sdk-core (~> 3, >= 3.127.0) 85 | aws-sigv4 (~> 1.1) 86 | aws-sdk-athena (1.54.0) 87 | aws-sdk-core (~> 3, >= 3.127.0) 88 | aws-sigv4 (~> 1.1) 89 | aws-sdk-auditmanager (1.25.0) 90 | aws-sdk-core (~> 3, >= 3.127.0) 91 | aws-sigv4 (~> 1.1) 92 | aws-sdk-augmentedairuntime (1.22.0) 93 | aws-sdk-core (~> 3, >= 3.127.0) 94 | aws-sigv4 (~> 1.1) 95 | aws-sdk-autoscaling (1.79.0) 96 | aws-sdk-core (~> 3, >= 3.127.0) 97 | aws-sigv4 (~> 1.1) 98 | aws-sdk-autoscalingplans (1.40.0) 99 | aws-sdk-core (~> 3, >= 3.127.0) 100 | aws-sigv4 (~> 1.1) 101 | aws-sdk-backup (1.45.0) 102 | aws-sdk-core (~> 3, >= 3.127.0) 103 | aws-sigv4 (~> 1.1) 104 | aws-sdk-backupgateway (1.4.0) 105 | aws-sdk-core (~> 3, >= 3.127.0) 106 | aws-sigv4 (~> 1.1) 107 | aws-sdk-batch (1.62.0) 108 | aws-sdk-core (~> 3, >= 3.127.0) 109 | aws-sigv4 (~> 1.1) 110 | aws-sdk-billingconductor (1.0.0) 111 | aws-sdk-core (~> 3, >= 3.127.0) 112 | aws-sigv4 (~> 1.1) 113 | aws-sdk-braket (1.19.0) 114 | aws-sdk-core (~> 3, >= 3.127.0) 115 | aws-sigv4 (~> 1.1) 116 | aws-sdk-budgets (1.50.0) 117 | aws-sdk-core (~> 3, >= 3.127.0) 118 | aws-sigv4 (~> 1.1) 119 | aws-sdk-chime (1.67.0) 120 | aws-sdk-core (~> 3, >= 3.127.0) 121 | aws-sigv4 (~> 1.1) 122 | aws-sdk-chimesdkidentity (1.9.0) 123 | aws-sdk-core (~> 3, >= 3.127.0) 124 | aws-sigv4 (~> 1.1) 125 | aws-sdk-chimesdkmediapipelines (1.0.0) 126 | aws-sdk-core (~> 3, >= 3.127.0) 127 | aws-sigv4 (~> 1.1) 128 | aws-sdk-chimesdkmeetings (1.13.0) 129 | aws-sdk-core (~> 3, >= 3.127.0) 130 | aws-sigv4 (~> 1.1) 131 | aws-sdk-chimesdkmessaging (1.11.0) 132 | aws-sdk-core (~> 3, >= 3.127.0) 133 | aws-sigv4 (~> 1.1) 134 | aws-sdk-cloud9 (1.45.0) 135 | aws-sdk-core (~> 3, >= 3.127.0) 136 | aws-sigv4 (~> 1.1) 137 | aws-sdk-cloudcontrolapi (1.8.0) 138 | aws-sdk-core (~> 3, >= 3.127.0) 139 | aws-sigv4 (~> 1.1) 140 | aws-sdk-clouddirectory (1.41.0) 141 | aws-sdk-core (~> 3, >= 3.127.0) 142 | aws-sigv4 (~> 1.1) 143 | aws-sdk-cloudformation (1.70.0) 144 | aws-sdk-core (~> 3, >= 3.127.0) 145 | aws-sigv4 (~> 1.1) 146 | aws-sdk-cloudfront (1.65.0) 147 | aws-sdk-core (~> 3, >= 3.127.0) 148 | aws-sigv4 (~> 1.1) 149 | aws-sdk-cloudhsm (1.39.0) 150 | aws-sdk-core (~> 3, >= 3.127.0) 151 | aws-sigv4 (~> 1.1) 152 | aws-sdk-cloudhsmv2 (1.42.0) 153 | aws-sdk-core (~> 3, >= 3.127.0) 154 | aws-sigv4 (~> 1.1) 155 | aws-sdk-cloudsearch (1.40.0) 156 | aws-sdk-core (~> 3, >= 3.127.0) 157 | aws-sigv4 (~> 1.1) 158 | aws-sdk-cloudsearchdomain (1.33.0) 159 | aws-sdk-core (~> 3, >= 3.127.0) 160 | aws-sigv4 (~> 1.1) 161 | aws-sdk-cloudtrail (1.49.0) 162 | aws-sdk-core (~> 3, >= 3.127.0) 163 | aws-sigv4 (~> 1.1) 164 | aws-sdk-cloudwatch (1.64.0) 165 | aws-sdk-core (~> 3, >= 3.127.0) 166 | aws-sigv4 (~> 1.1) 167 | aws-sdk-cloudwatchevents (1.57.0) 168 | aws-sdk-core (~> 3, >= 3.127.0) 169 | aws-sigv4 (~> 1.1) 170 | aws-sdk-cloudwatchevidently (1.6.0) 171 | aws-sdk-core (~> 3, >= 3.127.0) 172 | aws-sigv4 (~> 1.1) 173 | aws-sdk-cloudwatchlogs (1.53.0) 174 | aws-sdk-core (~> 3, >= 3.127.0) 175 | aws-sigv4 (~> 1.1) 176 | aws-sdk-cloudwatchrum (1.4.0) 177 | aws-sdk-core (~> 3, >= 3.127.0) 178 | aws-sigv4 (~> 1.1) 179 | aws-sdk-codeartifact (1.20.0) 180 | aws-sdk-core (~> 3, >= 3.127.0) 181 | aws-sigv4 (~> 1.1) 182 | aws-sdk-codebuild (1.88.0) 183 | aws-sdk-core (~> 3, >= 3.127.0) 184 | aws-sigv4 (~> 1.1) 185 | aws-sdk-codecommit (1.51.0) 186 | aws-sdk-core (~> 3, >= 3.127.0) 187 | aws-sigv4 (~> 1.1) 188 | aws-sdk-codedeploy (1.49.0) 189 | aws-sdk-core (~> 3, >= 3.127.0) 190 | aws-sigv4 (~> 1.1) 191 | aws-sdk-codeguruprofiler (1.24.0) 192 | aws-sdk-core (~> 3, >= 3.127.0) 193 | aws-sigv4 (~> 1.1) 194 | aws-sdk-codegurureviewer (1.31.0) 195 | aws-sdk-core (~> 3, >= 3.127.0) 196 | aws-sigv4 (~> 1.1) 197 | aws-sdk-codepipeline (1.53.0) 198 | aws-sdk-core (~> 3, >= 3.127.0) 199 | aws-sigv4 (~> 1.1) 200 | aws-sdk-codestar (1.38.0) 201 | aws-sdk-core (~> 3, >= 3.127.0) 202 | aws-sigv4 (~> 1.1) 203 | aws-sdk-codestarconnections (1.24.0) 204 | aws-sdk-core (~> 3, >= 3.127.0) 205 | aws-sigv4 (~> 1.1) 206 | aws-sdk-codestarnotifications (1.19.0) 207 | aws-sdk-core (~> 3, >= 3.127.0) 208 | aws-sigv4 (~> 1.1) 209 | aws-sdk-cognitoidentity (1.40.1) 210 | aws-sdk-core (~> 3, >= 3.127.0) 211 | aws-sigv4 (~> 1.1) 212 | aws-sdk-cognitoidentityprovider (1.67.0) 213 | aws-sdk-core (~> 3, >= 3.127.0) 214 | aws-sigv4 (~> 1.1) 215 | aws-sdk-cognitosync (1.36.0) 216 | aws-sdk-core (~> 3, >= 3.127.0) 217 | aws-sigv4 (~> 1.1) 218 | aws-sdk-comprehend (1.61.0) 219 | aws-sdk-core (~> 3, >= 3.127.0) 220 | aws-sigv4 (~> 1.1) 221 | aws-sdk-comprehendmedical (1.36.0) 222 | aws-sdk-core (~> 3, >= 3.127.0) 223 | aws-sigv4 (~> 1.1) 224 | aws-sdk-computeoptimizer (1.33.0) 225 | aws-sdk-core (~> 3, >= 3.127.0) 226 | aws-sigv4 (~> 1.1) 227 | aws-sdk-configservice (1.78.0) 228 | aws-sdk-core (~> 3, >= 3.127.0) 229 | aws-sigv4 (~> 1.1) 230 | aws-sdk-connect (1.74.0) 231 | aws-sdk-core (~> 3, >= 3.127.0) 232 | aws-sigv4 (~> 1.1) 233 | aws-sdk-connectcampaignservice (1.0.0) 234 | aws-sdk-core (~> 3, >= 3.127.0) 235 | aws-sigv4 (~> 1.1) 236 | aws-sdk-connectcontactlens (1.11.0) 237 | aws-sdk-core (~> 3, >= 3.127.0) 238 | aws-sigv4 (~> 1.1) 239 | aws-sdk-connectparticipant (1.22.0) 240 | aws-sdk-core (~> 3, >= 3.127.0) 241 | aws-sigv4 (~> 1.1) 242 | aws-sdk-connectwisdomservice (1.7.0) 243 | aws-sdk-core (~> 3, >= 3.127.0) 244 | aws-sigv4 (~> 1.1) 245 | aws-sdk-core (3.131.2) 246 | aws-eventstream (~> 1, >= 1.0.2) 247 | aws-partitions (~> 1, >= 1.525.0) 248 | aws-sigv4 (~> 1.1) 249 | jmespath (~> 1, >= 1.6.1) 250 | aws-sdk-costandusagereportservice (1.40.0) 251 | aws-sdk-core (~> 3, >= 3.127.0) 252 | aws-sigv4 (~> 1.1) 253 | aws-sdk-costexplorer (1.77.0) 254 | aws-sdk-core (~> 3, >= 3.127.0) 255 | aws-sigv4 (~> 1.1) 256 | aws-sdk-customerprofiles (1.21.0) 257 | aws-sdk-core (~> 3, >= 3.127.0) 258 | aws-sigv4 (~> 1.1) 259 | aws-sdk-databasemigrationservice (1.70.0) 260 | aws-sdk-core (~> 3, >= 3.127.0) 261 | aws-sigv4 (~> 1.1) 262 | aws-sdk-dataexchange (1.26.0) 263 | aws-sdk-core (~> 3, >= 3.127.0) 264 | aws-sigv4 (~> 1.1) 265 | aws-sdk-datapipeline (1.36.0) 266 | aws-sdk-core (~> 3, >= 3.127.0) 267 | aws-sigv4 (~> 1.1) 268 | aws-sdk-datasync (1.48.0) 269 | aws-sdk-core (~> 3, >= 3.127.0) 270 | aws-sigv4 (~> 1.1) 271 | aws-sdk-dax (1.39.0) 272 | aws-sdk-core (~> 3, >= 3.127.0) 273 | aws-sigv4 (~> 1.1) 274 | aws-sdk-detective (1.28.0) 275 | aws-sdk-core (~> 3, >= 3.127.0) 276 | aws-sigv4 (~> 1.1) 277 | aws-sdk-devicefarm (1.51.0) 278 | aws-sdk-core (~> 3, >= 3.127.0) 279 | aws-sigv4 (~> 1.1) 280 | aws-sdk-devopsguru (1.23.0) 281 | aws-sdk-core (~> 3, >= 3.127.0) 282 | aws-sigv4 (~> 1.1) 283 | aws-sdk-directconnect (1.54.0) 284 | aws-sdk-core (~> 3, >= 3.127.0) 285 | aws-sigv4 (~> 1.1) 286 | aws-sdk-directoryservice (1.50.0) 287 | aws-sdk-core (~> 3, >= 3.127.0) 288 | aws-sigv4 (~> 1.1) 289 | aws-sdk-dlm (1.50.0) 290 | aws-sdk-core (~> 3, >= 3.127.0) 291 | aws-sigv4 (~> 1.1) 292 | aws-sdk-docdb (1.42.0) 293 | aws-sdk-core (~> 3, >= 3.127.0) 294 | aws-sigv4 (~> 1.1) 295 | aws-sdk-drs (1.5.0) 296 | aws-sdk-core (~> 3, >= 3.127.0) 297 | aws-sigv4 (~> 1.1) 298 | aws-sdk-dynamodb (1.75.0) 299 | aws-sdk-core (~> 3, >= 3.127.0) 300 | aws-sigv4 (~> 1.1) 301 | aws-sdk-dynamodbstreams (1.39.0) 302 | aws-sdk-core (~> 3, >= 3.127.0) 303 | aws-sigv4 (~> 1.1) 304 | aws-sdk-ebs (1.26.0) 305 | aws-sdk-core (~> 3, >= 3.127.0) 306 | aws-sigv4 (~> 1.1) 307 | aws-sdk-ec2 (1.321.0) 308 | aws-sdk-core (~> 3, >= 3.127.0) 309 | aws-sigv4 (~> 1.1) 310 | aws-sdk-ec2instanceconnect (1.24.0) 311 | aws-sdk-core (~> 3, >= 3.127.0) 312 | aws-sigv4 (~> 1.1) 313 | aws-sdk-ecr (1.56.0) 314 | aws-sdk-core (~> 3, >= 3.127.0) 315 | aws-sigv4 (~> 1.1) 316 | aws-sdk-ecrpublic (1.12.0) 317 | aws-sdk-core (~> 3, >= 3.127.0) 318 | aws-sigv4 (~> 1.1) 319 | aws-sdk-ecs (1.100.0) 320 | aws-sdk-core (~> 3, >= 3.127.0) 321 | aws-sigv4 (~> 1.1) 322 | aws-sdk-efs (1.54.0) 323 | aws-sdk-core (~> 3, >= 3.127.0) 324 | aws-sigv4 (~> 1.1) 325 | aws-sdk-eks (1.75.0) 326 | aws-sdk-core (~> 3, >= 3.127.0) 327 | aws-sigv4 (~> 1.1) 328 | aws-sdk-elasticache (1.78.0) 329 | aws-sdk-core (~> 3, >= 3.127.0) 330 | aws-sigv4 (~> 1.1) 331 | aws-sdk-elasticbeanstalk (1.51.0) 332 | aws-sdk-core (~> 3, >= 3.127.0) 333 | aws-sigv4 (~> 1.1) 334 | aws-sdk-elasticinference (1.21.0) 335 | aws-sdk-core (~> 3, >= 3.127.0) 336 | aws-sigv4 (~> 1.1) 337 | aws-sdk-elasticloadbalancing (1.40.0) 338 | aws-sdk-core (~> 3, >= 3.127.0) 339 | aws-sigv4 (~> 1.1) 340 | aws-sdk-elasticloadbalancingv2 (1.78.0) 341 | aws-sdk-core (~> 3, >= 3.127.0) 342 | aws-sigv4 (~> 1.1) 343 | aws-sdk-elasticsearchservice (1.65.0) 344 | aws-sdk-core (~> 3, >= 3.127.0) 345 | aws-sigv4 (~> 1.1) 346 | aws-sdk-elastictranscoder (1.38.0) 347 | aws-sdk-core (~> 3, >= 3.127.0) 348 | aws-sigv4 (~> 1.1) 349 | aws-sdk-emr (1.62.0) 350 | aws-sdk-core (~> 3, >= 3.127.0) 351 | aws-sigv4 (~> 1.1) 352 | aws-sdk-emrcontainers (1.14.0) 353 | aws-sdk-core (~> 3, >= 3.127.0) 354 | aws-sigv4 (~> 1.1) 355 | aws-sdk-emrserverless (1.0.0) 356 | aws-sdk-core (~> 3, >= 3.127.0) 357 | aws-sigv4 (~> 1.1) 358 | aws-sdk-eventbridge (1.38.0) 359 | aws-sdk-core (~> 3, >= 3.127.0) 360 | aws-sigv4 (~> 1.1) 361 | aws-sdk-finspace (1.11.0) 362 | aws-sdk-core (~> 3, >= 3.127.0) 363 | aws-sigv4 (~> 1.1) 364 | aws-sdk-finspacedata (1.17.0) 365 | aws-sdk-core (~> 3, >= 3.127.0) 366 | aws-sigv4 (~> 1.1) 367 | aws-sdk-firehose (1.48.0) 368 | aws-sdk-core (~> 3, >= 3.127.0) 369 | aws-sigv4 (~> 1.1) 370 | aws-sdk-fis (1.13.0) 371 | aws-sdk-core (~> 3, >= 3.127.0) 372 | aws-sigv4 (~> 1.1) 373 | aws-sdk-fms (1.49.0) 374 | aws-sdk-core (~> 3, >= 3.127.0) 375 | aws-sigv4 (~> 1.1) 376 | aws-sdk-forecastqueryservice (1.21.0) 377 | aws-sdk-core (~> 3, >= 3.127.0) 378 | aws-sigv4 (~> 1.1) 379 | aws-sdk-forecastservice (1.36.0) 380 | aws-sdk-core (~> 3, >= 3.127.0) 381 | aws-sigv4 (~> 1.1) 382 | aws-sdk-frauddetector (1.33.0) 383 | aws-sdk-core (~> 3, >= 3.127.0) 384 | aws-sigv4 (~> 1.1) 385 | aws-sdk-fsx (1.56.0) 386 | aws-sdk-core (~> 3, >= 3.127.0) 387 | aws-sigv4 (~> 1.1) 388 | aws-sdk-gamelift (1.57.0) 389 | aws-sdk-core (~> 3, >= 3.127.0) 390 | aws-sigv4 (~> 1.1) 391 | aws-sdk-gamesparks (1.1.0) 392 | aws-sdk-core (~> 3, >= 3.127.0) 393 | aws-sigv4 (~> 1.1) 394 | aws-sdk-glacier (1.46.0) 395 | aws-sdk-core (~> 3, >= 3.127.0) 396 | aws-sigv4 (~> 1.1) 397 | aws-sdk-globalaccelerator (1.39.0) 398 | aws-sdk-core (~> 3, >= 3.127.0) 399 | aws-sigv4 (~> 1.1) 400 | aws-sdk-glue (1.114.0) 401 | aws-sdk-core (~> 3, >= 3.127.0) 402 | aws-sigv4 (~> 1.1) 403 | aws-sdk-gluedatabrew (1.22.0) 404 | aws-sdk-core (~> 3, >= 3.127.0) 405 | aws-sigv4 (~> 1.1) 406 | aws-sdk-greengrass (1.49.0) 407 | aws-sdk-core (~> 3, >= 3.127.0) 408 | aws-sigv4 (~> 1.1) 409 | aws-sdk-greengrassv2 (1.18.0) 410 | aws-sdk-core (~> 3, >= 3.127.0) 411 | aws-sigv4 (~> 1.1) 412 | aws-sdk-groundstation (1.27.0) 413 | aws-sdk-core (~> 3, >= 3.127.0) 414 | aws-sigv4 (~> 1.1) 415 | aws-sdk-guardduty (1.58.0) 416 | aws-sdk-core (~> 3, >= 3.127.0) 417 | aws-sigv4 (~> 1.1) 418 | aws-sdk-health (1.47.0) 419 | aws-sdk-core (~> 3, >= 3.127.0) 420 | aws-sigv4 (~> 1.1) 421 | aws-sdk-healthlake (1.13.0) 422 | aws-sdk-core (~> 3, >= 3.127.0) 423 | aws-sigv4 (~> 1.1) 424 | aws-sdk-honeycode (1.17.0) 425 | aws-sdk-core (~> 3, >= 3.127.0) 426 | aws-sigv4 (~> 1.1) 427 | aws-sdk-iam (1.69.0) 428 | aws-sdk-core (~> 3, >= 3.127.0) 429 | aws-sigv4 (~> 1.1) 430 | aws-sdk-identitystore (1.15.0) 431 | aws-sdk-core (~> 3, >= 3.127.0) 432 | aws-sigv4 (~> 1.1) 433 | aws-sdk-imagebuilder (1.40.0) 434 | aws-sdk-core (~> 3, >= 3.127.0) 435 | aws-sigv4 (~> 1.1) 436 | aws-sdk-importexport (1.35.0) 437 | aws-sdk-core (~> 3, >= 3.127.0) 438 | aws-sigv2 (~> 1.0) 439 | aws-sdk-inspector (1.43.0) 440 | aws-sdk-core (~> 3, >= 3.127.0) 441 | aws-sigv4 (~> 1.1) 442 | aws-sdk-inspector2 (1.4.0) 443 | aws-sdk-core (~> 3, >= 3.127.0) 444 | aws-sigv4 (~> 1.1) 445 | aws-sdk-iot (1.92.0) 446 | aws-sdk-core (~> 3, >= 3.127.0) 447 | aws-sigv4 (~> 1.1) 448 | aws-sdk-iot1clickdevicesservice (1.37.0) 449 | aws-sdk-core (~> 3, >= 3.127.0) 450 | aws-sigv4 (~> 1.1) 451 | aws-sdk-iot1clickprojects (1.37.0) 452 | aws-sdk-core (~> 3, >= 3.127.0) 453 | aws-sigv4 (~> 1.1) 454 | aws-sdk-iotanalytics (1.49.0) 455 | aws-sdk-core (~> 3, >= 3.127.0) 456 | aws-sigv4 (~> 1.1) 457 | aws-sdk-iotdataplane (1.39.0) 458 | aws-sdk-core (~> 3, >= 3.127.0) 459 | aws-sigv4 (~> 1.1) 460 | aws-sdk-iotdeviceadvisor (1.14.0) 461 | aws-sdk-core (~> 3, >= 3.127.0) 462 | aws-sigv4 (~> 1.1) 463 | aws-sdk-iotevents (1.33.0) 464 | aws-sdk-core (~> 3, >= 3.127.0) 465 | aws-sigv4 (~> 1.1) 466 | aws-sdk-ioteventsdata (1.27.0) 467 | aws-sdk-core (~> 3, >= 3.127.0) 468 | aws-sigv4 (~> 1.1) 469 | aws-sdk-iotfleethub (1.11.0) 470 | aws-sdk-core (~> 3, >= 3.127.0) 471 | aws-sigv4 (~> 1.1) 472 | aws-sdk-iotjobsdataplane (1.36.0) 473 | aws-sdk-core (~> 3, >= 3.127.0) 474 | aws-sigv4 (~> 1.1) 475 | aws-sdk-iotsecuretunneling (1.21.0) 476 | aws-sdk-core (~> 3, >= 3.127.0) 477 | aws-sigv4 (~> 1.1) 478 | aws-sdk-iotsitewise (1.42.0) 479 | aws-sdk-core (~> 3, >= 3.127.0) 480 | aws-sigv4 (~> 1.1) 481 | aws-sdk-iotthingsgraph (1.23.0) 482 | aws-sdk-core (~> 3, >= 3.127.0) 483 | aws-sigv4 (~> 1.1) 484 | aws-sdk-iottwinmaker (1.5.0) 485 | aws-sdk-core (~> 3, >= 3.127.0) 486 | aws-sigv4 (~> 1.1) 487 | aws-sdk-iotwireless (1.24.0) 488 | aws-sdk-core (~> 3, >= 3.127.0) 489 | aws-sigv4 (~> 1.1) 490 | aws-sdk-ivs (1.20.0) 491 | aws-sdk-core (~> 3, >= 3.127.0) 492 | aws-sigv4 (~> 1.1) 493 | aws-sdk-ivschat (1.2.0) 494 | aws-sdk-core (~> 3, >= 3.127.0) 495 | aws-sigv4 (~> 1.1) 496 | aws-sdk-kafka (1.50.0) 497 | aws-sdk-core (~> 3, >= 3.127.0) 498 | aws-sigv4 (~> 1.1) 499 | aws-sdk-kafkaconnect (1.7.0) 500 | aws-sdk-core (~> 3, >= 3.127.0) 501 | aws-sigv4 (~> 1.1) 502 | aws-sdk-kendra (1.53.0) 503 | aws-sdk-core (~> 3, >= 3.127.0) 504 | aws-sigv4 (~> 1.1) 505 | aws-sdk-keyspaces (1.2.0) 506 | aws-sdk-core (~> 3, >= 3.127.0) 507 | aws-sigv4 (~> 1.1) 508 | aws-sdk-kinesis (1.41.0) 509 | aws-sdk-core (~> 3, >= 3.127.0) 510 | aws-sigv4 (~> 1.1) 511 | aws-sdk-kinesisanalytics (1.40.0) 512 | aws-sdk-core (~> 3, >= 3.127.0) 513 | aws-sigv4 (~> 1.1) 514 | aws-sdk-kinesisanalyticsv2 (1.40.0) 515 | aws-sdk-core (~> 3, >= 3.127.0) 516 | aws-sigv4 (~> 1.1) 517 | aws-sdk-kinesisvideo (1.42.0) 518 | aws-sdk-core (~> 3, >= 3.127.0) 519 | aws-sigv4 (~> 1.1) 520 | aws-sdk-kinesisvideoarchivedmedia (1.44.0) 521 | aws-sdk-core (~> 3, >= 3.127.0) 522 | aws-sigv4 (~> 1.1) 523 | aws-sdk-kinesisvideomedia (1.37.0) 524 | aws-sdk-core (~> 3, >= 3.127.0) 525 | aws-sigv4 (~> 1.1) 526 | aws-sdk-kinesisvideosignalingchannels (1.19.0) 527 | aws-sdk-core (~> 3, >= 3.127.0) 528 | aws-sigv4 (~> 1.1) 529 | aws-sdk-kms (1.57.0) 530 | aws-sdk-core (~> 3, >= 3.127.0) 531 | aws-sigv4 (~> 1.1) 532 | aws-sdk-lakeformation (1.26.0) 533 | aws-sdk-core (~> 3, >= 3.127.0) 534 | aws-sigv4 (~> 1.1) 535 | aws-sdk-lambda (1.84.0) 536 | aws-sdk-core (~> 3, >= 3.127.0) 537 | aws-sigv4 (~> 1.1) 538 | aws-sdk-lambdapreview (1.35.0) 539 | aws-sdk-core (~> 3, >= 3.127.0) 540 | aws-sigv4 (~> 1.1) 541 | aws-sdk-lex (1.45.0) 542 | aws-sdk-core (~> 3, >= 3.127.0) 543 | aws-sigv4 (~> 1.1) 544 | aws-sdk-lexmodelbuildingservice (1.57.0) 545 | aws-sdk-core (~> 3, >= 3.127.0) 546 | aws-sigv4 (~> 1.1) 547 | aws-sdk-lexmodelsv2 (1.24.0) 548 | aws-sdk-core (~> 3, >= 3.127.0) 549 | aws-sigv4 (~> 1.1) 550 | aws-sdk-lexruntimev2 (1.15.0) 551 | aws-sdk-core (~> 3, >= 3.127.0) 552 | aws-sigv4 (~> 1.1) 553 | aws-sdk-licensemanager (1.40.0) 554 | aws-sdk-core (~> 3, >= 3.127.0) 555 | aws-sigv4 (~> 1.1) 556 | aws-sdk-lightsail (1.68.0) 557 | aws-sdk-core (~> 3, >= 3.127.0) 558 | aws-sigv4 (~> 1.1) 559 | aws-sdk-locationservice (1.22.0) 560 | aws-sdk-core (~> 3, >= 3.127.0) 561 | aws-sigv4 (~> 1.1) 562 | aws-sdk-lookoutequipment (1.12.0) 563 | aws-sdk-core (~> 3, >= 3.127.0) 564 | aws-sigv4 (~> 1.1) 565 | aws-sdk-lookoutforvision (1.14.0) 566 | aws-sdk-core (~> 3, >= 3.127.0) 567 | aws-sigv4 (~> 1.1) 568 | aws-sdk-lookoutmetrics (1.20.0) 569 | aws-sdk-core (~> 3, >= 3.127.0) 570 | aws-sigv4 (~> 1.1) 571 | aws-sdk-machinelearning (1.37.0) 572 | aws-sdk-core (~> 3, >= 3.127.0) 573 | aws-sigv4 (~> 1.1) 574 | aws-sdk-macie (1.38.0) 575 | aws-sdk-core (~> 3, >= 3.127.0) 576 | aws-sigv4 (~> 1.1) 577 | aws-sdk-macie2 (1.45.0) 578 | aws-sdk-core (~> 3, >= 3.127.0) 579 | aws-sigv4 (~> 1.1) 580 | aws-sdk-mainframemodernization (1.0.0) 581 | aws-sdk-core (~> 3, >= 3.127.0) 582 | aws-sigv4 (~> 1.1) 583 | aws-sdk-managedblockchain (1.32.0) 584 | aws-sdk-core (~> 3, >= 3.127.0) 585 | aws-sigv4 (~> 1.1) 586 | aws-sdk-managedgrafana (1.8.0) 587 | aws-sdk-core (~> 3, >= 3.127.0) 588 | aws-sigv4 (~> 1.1) 589 | aws-sdk-marketplacecatalog (1.21.0) 590 | aws-sdk-core (~> 3, >= 3.127.0) 591 | aws-sigv4 (~> 1.1) 592 | aws-sdk-marketplacecommerceanalytics (1.41.0) 593 | aws-sdk-core (~> 3, >= 3.127.0) 594 | aws-sigv4 (~> 1.1) 595 | aws-sdk-marketplaceentitlementservice (1.35.0) 596 | aws-sdk-core (~> 3, >= 3.127.0) 597 | aws-sigv4 (~> 1.1) 598 | aws-sdk-marketplacemetering (1.44.0) 599 | aws-sdk-core (~> 3, >= 3.127.0) 600 | aws-sigv4 (~> 1.1) 601 | aws-sdk-mediaconnect (1.44.0) 602 | aws-sdk-core (~> 3, >= 3.127.0) 603 | aws-sigv4 (~> 1.1) 604 | aws-sdk-mediaconvert (1.92.0) 605 | aws-sdk-core (~> 3, >= 3.127.0) 606 | aws-sigv4 (~> 1.1) 607 | aws-sdk-medialive (1.87.0) 608 | aws-sdk-core (~> 3, >= 3.127.0) 609 | aws-sigv4 (~> 1.1) 610 | aws-sdk-mediapackage (1.53.0) 611 | aws-sdk-core (~> 3, >= 3.127.0) 612 | aws-sigv4 (~> 1.1) 613 | aws-sdk-mediapackagevod (1.36.0) 614 | aws-sdk-core (~> 3, >= 3.127.0) 615 | aws-sigv4 (~> 1.1) 616 | aws-sdk-mediastore (1.41.0) 617 | aws-sdk-core (~> 3, >= 3.127.0) 618 | aws-sigv4 (~> 1.1) 619 | aws-sdk-mediastoredata (1.38.0) 620 | aws-sdk-core (~> 3, >= 3.127.0) 621 | aws-sigv4 (~> 1.1) 622 | aws-sdk-mediatailor (1.55.0) 623 | aws-sdk-core (~> 3, >= 3.127.0) 624 | aws-sigv4 (~> 1.1) 625 | aws-sdk-memorydb (1.8.0) 626 | aws-sdk-core (~> 3, >= 3.127.0) 627 | aws-sigv4 (~> 1.1) 628 | aws-sdk-mgn (1.14.0) 629 | aws-sdk-core (~> 3, >= 3.127.0) 630 | aws-sigv4 (~> 1.1) 631 | aws-sdk-migrationhub (1.40.0) 632 | aws-sdk-core (~> 3, >= 3.127.0) 633 | aws-sigv4 (~> 1.1) 634 | aws-sdk-migrationhubconfig (1.20.0) 635 | aws-sdk-core (~> 3, >= 3.127.0) 636 | aws-sigv4 (~> 1.1) 637 | aws-sdk-migrationhubrefactorspaces (1.7.0) 638 | aws-sdk-core (~> 3, >= 3.127.0) 639 | aws-sigv4 (~> 1.1) 640 | aws-sdk-migrationhubstrategyrecommendations (1.4.0) 641 | aws-sdk-core (~> 3, >= 3.127.0) 642 | aws-sigv4 (~> 1.1) 643 | aws-sdk-mobile (1.35.0) 644 | aws-sdk-core (~> 3, >= 3.127.0) 645 | aws-sigv4 (~> 1.1) 646 | aws-sdk-mq (1.47.0) 647 | aws-sdk-core (~> 3, >= 3.127.0) 648 | aws-sigv4 (~> 1.1) 649 | aws-sdk-mturk (1.40.0) 650 | aws-sdk-core (~> 3, >= 3.127.0) 651 | aws-sigv4 (~> 1.1) 652 | aws-sdk-mwaa (1.16.0) 653 | aws-sdk-core (~> 3, >= 3.127.0) 654 | aws-sigv4 (~> 1.1) 655 | aws-sdk-neptune (1.46.0) 656 | aws-sdk-core (~> 3, >= 3.127.0) 657 | aws-sigv4 (~> 1.1) 658 | aws-sdk-networkfirewall (1.17.0) 659 | aws-sdk-core (~> 3, >= 3.127.0) 660 | aws-sigv4 (~> 1.1) 661 | aws-sdk-networkmanager (1.24.0) 662 | aws-sdk-core (~> 3, >= 3.127.0) 663 | aws-sigv4 (~> 1.1) 664 | aws-sdk-nimblestudio (1.13.0) 665 | aws-sdk-core (~> 3, >= 3.127.0) 666 | aws-sigv4 (~> 1.1) 667 | aws-sdk-opensearchservice (1.10.0) 668 | aws-sdk-core (~> 3, >= 3.127.0) 669 | aws-sigv4 (~> 1.1) 670 | aws-sdk-opsworks (1.41.0) 671 | aws-sdk-core (~> 3, >= 3.127.0) 672 | aws-sigv4 (~> 1.1) 673 | aws-sdk-opsworkscm (1.52.0) 674 | aws-sdk-core (~> 3, >= 3.127.0) 675 | aws-sigv4 (~> 1.1) 676 | aws-sdk-organizations (1.70.0) 677 | aws-sdk-core (~> 3, >= 3.127.0) 678 | aws-sigv4 (~> 1.1) 679 | aws-sdk-outposts (1.34.0) 680 | aws-sdk-core (~> 3, >= 3.127.0) 681 | aws-sigv4 (~> 1.1) 682 | aws-sdk-panorama (1.7.0) 683 | aws-sdk-core (~> 3, >= 3.127.0) 684 | aws-sigv4 (~> 1.1) 685 | aws-sdk-personalize (1.42.0) 686 | aws-sdk-core (~> 3, >= 3.127.0) 687 | aws-sigv4 (~> 1.1) 688 | aws-sdk-personalizeevents (1.27.0) 689 | aws-sdk-core (~> 3, >= 3.127.0) 690 | aws-sigv4 (~> 1.1) 691 | aws-sdk-personalizeruntime (1.32.0) 692 | aws-sdk-core (~> 3, >= 3.127.0) 693 | aws-sigv4 (~> 1.1) 694 | aws-sdk-pi (1.39.0) 695 | aws-sdk-core (~> 3, >= 3.127.0) 696 | aws-sigv4 (~> 1.1) 697 | aws-sdk-pinpoint (1.67.0) 698 | aws-sdk-core (~> 3, >= 3.127.0) 699 | aws-sigv4 (~> 1.1) 700 | aws-sdk-pinpointemail (1.35.0) 701 | aws-sdk-core (~> 3, >= 3.127.0) 702 | aws-sigv4 (~> 1.1) 703 | aws-sdk-pinpointsmsvoice (1.32.0) 704 | aws-sdk-core (~> 3, >= 3.127.0) 705 | aws-sigv4 (~> 1.1) 706 | aws-sdk-pinpointsmsvoicev2 (1.0.0) 707 | aws-sdk-core (~> 3, >= 3.127.0) 708 | aws-sigv4 (~> 1.1) 709 | aws-sdk-polly (1.56.0) 710 | aws-sdk-core (~> 3, >= 3.127.0) 711 | aws-sigv4 (~> 1.1) 712 | aws-sdk-pricing (1.40.0) 713 | aws-sdk-core (~> 3, >= 3.127.0) 714 | aws-sigv4 (~> 1.1) 715 | aws-sdk-prometheusservice (1.14.0) 716 | aws-sdk-core (~> 3, >= 3.127.0) 717 | aws-sigv4 (~> 1.1) 718 | aws-sdk-proton (1.16.0) 719 | aws-sdk-core (~> 3, >= 3.127.0) 720 | aws-sigv4 (~> 1.1) 721 | aws-sdk-qldb (1.25.0) 722 | aws-sdk-core (~> 3, >= 3.127.0) 723 | aws-sigv4 (~> 1.1) 724 | aws-sdk-qldbsession (1.22.0) 725 | aws-sdk-core (~> 3, >= 3.127.0) 726 | aws-sigv4 (~> 1.1) 727 | aws-sdk-quicksight (1.66.0) 728 | aws-sdk-core (~> 3, >= 3.127.0) 729 | aws-sigv4 (~> 1.1) 730 | aws-sdk-ram (1.39.0) 731 | aws-sdk-core (~> 3, >= 3.127.0) 732 | aws-sigv4 (~> 1.1) 733 | aws-sdk-rds (1.148.0) 734 | aws-sdk-core (~> 3, >= 3.127.0) 735 | aws-sigv4 (~> 1.1) 736 | aws-sdk-rdsdataservice (1.36.0) 737 | aws-sdk-core (~> 3, >= 3.127.0) 738 | aws-sigv4 (~> 1.1) 739 | aws-sdk-recyclebin (1.5.0) 740 | aws-sdk-core (~> 3, >= 3.127.0) 741 | aws-sigv4 (~> 1.1) 742 | aws-sdk-redshift (1.84.0) 743 | aws-sdk-core (~> 3, >= 3.127.0) 744 | aws-sigv4 (~> 1.1) 745 | aws-sdk-redshiftdataapiservice (1.21.0) 746 | aws-sdk-core (~> 3, >= 3.127.0) 747 | aws-sigv4 (~> 1.1) 748 | aws-sdk-redshiftserverless (1.3.0) 749 | aws-sdk-core (~> 3, >= 3.127.0) 750 | aws-sigv4 (~> 1.1) 751 | aws-sdk-rekognition (1.68.0) 752 | aws-sdk-core (~> 3, >= 3.127.0) 753 | aws-sigv4 (~> 1.1) 754 | aws-sdk-resiliencehub (1.5.0) 755 | aws-sdk-core (~> 3, >= 3.127.0) 756 | aws-sigv4 (~> 1.1) 757 | aws-sdk-resourcegroups (1.45.0) 758 | aws-sdk-core (~> 3, >= 3.127.0) 759 | aws-sigv4 (~> 1.1) 760 | aws-sdk-resourcegroupstaggingapi (1.47.0) 761 | aws-sdk-core (~> 3, >= 3.127.0) 762 | aws-sigv4 (~> 1.1) 763 | aws-sdk-resources (3.136.0) 764 | aws-sdk-accessanalyzer (~> 1) 765 | aws-sdk-account (~> 1) 766 | aws-sdk-acm (~> 1) 767 | aws-sdk-acmpca (~> 1) 768 | aws-sdk-alexaforbusiness (~> 1) 769 | aws-sdk-amplify (~> 1) 770 | aws-sdk-amplifybackend (~> 1) 771 | aws-sdk-amplifyuibuilder (~> 1) 772 | aws-sdk-apigateway (~> 1) 773 | aws-sdk-apigatewaymanagementapi (~> 1) 774 | aws-sdk-apigatewayv2 (~> 1) 775 | aws-sdk-appconfig (~> 1) 776 | aws-sdk-appconfigdata (~> 1) 777 | aws-sdk-appflow (~> 1) 778 | aws-sdk-appintegrationsservice (~> 1) 779 | aws-sdk-applicationautoscaling (~> 1) 780 | aws-sdk-applicationcostprofiler (~> 1) 781 | aws-sdk-applicationdiscoveryservice (~> 1) 782 | aws-sdk-applicationinsights (~> 1) 783 | aws-sdk-appmesh (~> 1) 784 | aws-sdk-appregistry (~> 1) 785 | aws-sdk-apprunner (~> 1) 786 | aws-sdk-appstream (~> 1) 787 | aws-sdk-appsync (~> 1) 788 | aws-sdk-athena (~> 1) 789 | aws-sdk-auditmanager (~> 1) 790 | aws-sdk-augmentedairuntime (~> 1) 791 | aws-sdk-autoscaling (~> 1) 792 | aws-sdk-autoscalingplans (~> 1) 793 | aws-sdk-backup (~> 1) 794 | aws-sdk-backupgateway (~> 1) 795 | aws-sdk-batch (~> 1) 796 | aws-sdk-billingconductor (~> 1) 797 | aws-sdk-braket (~> 1) 798 | aws-sdk-budgets (~> 1) 799 | aws-sdk-chime (~> 1) 800 | aws-sdk-chimesdkidentity (~> 1) 801 | aws-sdk-chimesdkmediapipelines (~> 1) 802 | aws-sdk-chimesdkmeetings (~> 1) 803 | aws-sdk-chimesdkmessaging (~> 1) 804 | aws-sdk-cloud9 (~> 1) 805 | aws-sdk-cloudcontrolapi (~> 1) 806 | aws-sdk-clouddirectory (~> 1) 807 | aws-sdk-cloudformation (~> 1) 808 | aws-sdk-cloudfront (~> 1) 809 | aws-sdk-cloudhsm (~> 1) 810 | aws-sdk-cloudhsmv2 (~> 1) 811 | aws-sdk-cloudsearch (~> 1) 812 | aws-sdk-cloudsearchdomain (~> 1) 813 | aws-sdk-cloudtrail (~> 1) 814 | aws-sdk-cloudwatch (~> 1) 815 | aws-sdk-cloudwatchevents (~> 1) 816 | aws-sdk-cloudwatchevidently (~> 1) 817 | aws-sdk-cloudwatchlogs (~> 1) 818 | aws-sdk-cloudwatchrum (~> 1) 819 | aws-sdk-codeartifact (~> 1) 820 | aws-sdk-codebuild (~> 1) 821 | aws-sdk-codecommit (~> 1) 822 | aws-sdk-codedeploy (~> 1) 823 | aws-sdk-codeguruprofiler (~> 1) 824 | aws-sdk-codegurureviewer (~> 1) 825 | aws-sdk-codepipeline (~> 1) 826 | aws-sdk-codestar (~> 1) 827 | aws-sdk-codestarconnections (~> 1) 828 | aws-sdk-codestarnotifications (~> 1) 829 | aws-sdk-cognitoidentity (~> 1) 830 | aws-sdk-cognitoidentityprovider (~> 1) 831 | aws-sdk-cognitosync (~> 1) 832 | aws-sdk-comprehend (~> 1) 833 | aws-sdk-comprehendmedical (~> 1) 834 | aws-sdk-computeoptimizer (~> 1) 835 | aws-sdk-configservice (~> 1) 836 | aws-sdk-connect (~> 1) 837 | aws-sdk-connectcampaignservice (~> 1) 838 | aws-sdk-connectcontactlens (~> 1) 839 | aws-sdk-connectparticipant (~> 1) 840 | aws-sdk-connectwisdomservice (~> 1) 841 | aws-sdk-costandusagereportservice (~> 1) 842 | aws-sdk-costexplorer (~> 1) 843 | aws-sdk-customerprofiles (~> 1) 844 | aws-sdk-databasemigrationservice (~> 1) 845 | aws-sdk-dataexchange (~> 1) 846 | aws-sdk-datapipeline (~> 1) 847 | aws-sdk-datasync (~> 1) 848 | aws-sdk-dax (~> 1) 849 | aws-sdk-detective (~> 1) 850 | aws-sdk-devicefarm (~> 1) 851 | aws-sdk-devopsguru (~> 1) 852 | aws-sdk-directconnect (~> 1) 853 | aws-sdk-directoryservice (~> 1) 854 | aws-sdk-dlm (~> 1) 855 | aws-sdk-docdb (~> 1) 856 | aws-sdk-drs (~> 1) 857 | aws-sdk-dynamodb (~> 1) 858 | aws-sdk-dynamodbstreams (~> 1) 859 | aws-sdk-ebs (~> 1) 860 | aws-sdk-ec2 (~> 1) 861 | aws-sdk-ec2instanceconnect (~> 1) 862 | aws-sdk-ecr (~> 1) 863 | aws-sdk-ecrpublic (~> 1) 864 | aws-sdk-ecs (~> 1) 865 | aws-sdk-efs (~> 1) 866 | aws-sdk-eks (~> 1) 867 | aws-sdk-elasticache (~> 1) 868 | aws-sdk-elasticbeanstalk (~> 1) 869 | aws-sdk-elasticinference (~> 1) 870 | aws-sdk-elasticloadbalancing (~> 1) 871 | aws-sdk-elasticloadbalancingv2 (~> 1) 872 | aws-sdk-elasticsearchservice (~> 1) 873 | aws-sdk-elastictranscoder (~> 1) 874 | aws-sdk-emr (~> 1) 875 | aws-sdk-emrcontainers (~> 1) 876 | aws-sdk-emrserverless (~> 1) 877 | aws-sdk-eventbridge (~> 1) 878 | aws-sdk-finspace (~> 1) 879 | aws-sdk-finspacedata (~> 1) 880 | aws-sdk-firehose (~> 1) 881 | aws-sdk-fis (~> 1) 882 | aws-sdk-fms (~> 1) 883 | aws-sdk-forecastqueryservice (~> 1) 884 | aws-sdk-forecastservice (~> 1) 885 | aws-sdk-frauddetector (~> 1) 886 | aws-sdk-fsx (~> 1) 887 | aws-sdk-gamelift (~> 1) 888 | aws-sdk-gamesparks (~> 1) 889 | aws-sdk-glacier (~> 1) 890 | aws-sdk-globalaccelerator (~> 1) 891 | aws-sdk-glue (~> 1) 892 | aws-sdk-gluedatabrew (~> 1) 893 | aws-sdk-greengrass (~> 1) 894 | aws-sdk-greengrassv2 (~> 1) 895 | aws-sdk-groundstation (~> 1) 896 | aws-sdk-guardduty (~> 1) 897 | aws-sdk-health (~> 1) 898 | aws-sdk-healthlake (~> 1) 899 | aws-sdk-honeycode (~> 1) 900 | aws-sdk-iam (~> 1) 901 | aws-sdk-identitystore (~> 1) 902 | aws-sdk-imagebuilder (~> 1) 903 | aws-sdk-importexport (~> 1) 904 | aws-sdk-inspector (~> 1) 905 | aws-sdk-inspector2 (~> 1) 906 | aws-sdk-iot (~> 1) 907 | aws-sdk-iot1clickdevicesservice (~> 1) 908 | aws-sdk-iot1clickprojects (~> 1) 909 | aws-sdk-iotanalytics (~> 1) 910 | aws-sdk-iotdataplane (~> 1) 911 | aws-sdk-iotdeviceadvisor (~> 1) 912 | aws-sdk-iotevents (~> 1) 913 | aws-sdk-ioteventsdata (~> 1) 914 | aws-sdk-iotfleethub (~> 1) 915 | aws-sdk-iotjobsdataplane (~> 1) 916 | aws-sdk-iotsecuretunneling (~> 1) 917 | aws-sdk-iotsitewise (~> 1) 918 | aws-sdk-iotthingsgraph (~> 1) 919 | aws-sdk-iottwinmaker (~> 1) 920 | aws-sdk-iotwireless (~> 1) 921 | aws-sdk-ivs (~> 1) 922 | aws-sdk-ivschat (~> 1) 923 | aws-sdk-kafka (~> 1) 924 | aws-sdk-kafkaconnect (~> 1) 925 | aws-sdk-kendra (~> 1) 926 | aws-sdk-keyspaces (~> 1) 927 | aws-sdk-kinesis (~> 1) 928 | aws-sdk-kinesisanalytics (~> 1) 929 | aws-sdk-kinesisanalyticsv2 (~> 1) 930 | aws-sdk-kinesisvideo (~> 1) 931 | aws-sdk-kinesisvideoarchivedmedia (~> 1) 932 | aws-sdk-kinesisvideomedia (~> 1) 933 | aws-sdk-kinesisvideosignalingchannels (~> 1) 934 | aws-sdk-kms (~> 1) 935 | aws-sdk-lakeformation (~> 1) 936 | aws-sdk-lambda (~> 1) 937 | aws-sdk-lambdapreview (~> 1) 938 | aws-sdk-lex (~> 1) 939 | aws-sdk-lexmodelbuildingservice (~> 1) 940 | aws-sdk-lexmodelsv2 (~> 1) 941 | aws-sdk-lexruntimev2 (~> 1) 942 | aws-sdk-licensemanager (~> 1) 943 | aws-sdk-lightsail (~> 1) 944 | aws-sdk-locationservice (~> 1) 945 | aws-sdk-lookoutequipment (~> 1) 946 | aws-sdk-lookoutforvision (~> 1) 947 | aws-sdk-lookoutmetrics (~> 1) 948 | aws-sdk-machinelearning (~> 1) 949 | aws-sdk-macie (~> 1) 950 | aws-sdk-macie2 (~> 1) 951 | aws-sdk-mainframemodernization (~> 1) 952 | aws-sdk-managedblockchain (~> 1) 953 | aws-sdk-managedgrafana (~> 1) 954 | aws-sdk-marketplacecatalog (~> 1) 955 | aws-sdk-marketplacecommerceanalytics (~> 1) 956 | aws-sdk-marketplaceentitlementservice (~> 1) 957 | aws-sdk-marketplacemetering (~> 1) 958 | aws-sdk-mediaconnect (~> 1) 959 | aws-sdk-mediaconvert (~> 1) 960 | aws-sdk-medialive (~> 1) 961 | aws-sdk-mediapackage (~> 1) 962 | aws-sdk-mediapackagevod (~> 1) 963 | aws-sdk-mediastore (~> 1) 964 | aws-sdk-mediastoredata (~> 1) 965 | aws-sdk-mediatailor (~> 1) 966 | aws-sdk-memorydb (~> 1) 967 | aws-sdk-mgn (~> 1) 968 | aws-sdk-migrationhub (~> 1) 969 | aws-sdk-migrationhubconfig (~> 1) 970 | aws-sdk-migrationhubrefactorspaces (~> 1) 971 | aws-sdk-migrationhubstrategyrecommendations (~> 1) 972 | aws-sdk-mobile (~> 1) 973 | aws-sdk-mq (~> 1) 974 | aws-sdk-mturk (~> 1) 975 | aws-sdk-mwaa (~> 1) 976 | aws-sdk-neptune (~> 1) 977 | aws-sdk-networkfirewall (~> 1) 978 | aws-sdk-networkmanager (~> 1) 979 | aws-sdk-nimblestudio (~> 1) 980 | aws-sdk-opensearchservice (~> 1) 981 | aws-sdk-opsworks (~> 1) 982 | aws-sdk-opsworkscm (~> 1) 983 | aws-sdk-organizations (~> 1) 984 | aws-sdk-outposts (~> 1) 985 | aws-sdk-panorama (~> 1) 986 | aws-sdk-personalize (~> 1) 987 | aws-sdk-personalizeevents (~> 1) 988 | aws-sdk-personalizeruntime (~> 1) 989 | aws-sdk-pi (~> 1) 990 | aws-sdk-pinpoint (~> 1) 991 | aws-sdk-pinpointemail (~> 1) 992 | aws-sdk-pinpointsmsvoice (~> 1) 993 | aws-sdk-pinpointsmsvoicev2 (~> 1) 994 | aws-sdk-polly (~> 1) 995 | aws-sdk-pricing (~> 1) 996 | aws-sdk-prometheusservice (~> 1) 997 | aws-sdk-proton (~> 1) 998 | aws-sdk-qldb (~> 1) 999 | aws-sdk-qldbsession (~> 1) 1000 | aws-sdk-quicksight (~> 1) 1001 | aws-sdk-ram (~> 1) 1002 | aws-sdk-rds (~> 1) 1003 | aws-sdk-rdsdataservice (~> 1) 1004 | aws-sdk-recyclebin (~> 1) 1005 | aws-sdk-redshift (~> 1) 1006 | aws-sdk-redshiftdataapiservice (~> 1) 1007 | aws-sdk-redshiftserverless (~> 1) 1008 | aws-sdk-rekognition (~> 1) 1009 | aws-sdk-resiliencehub (~> 1) 1010 | aws-sdk-resourcegroups (~> 1) 1011 | aws-sdk-resourcegroupstaggingapi (~> 1) 1012 | aws-sdk-robomaker (~> 1) 1013 | aws-sdk-rolesanywhere (~> 1) 1014 | aws-sdk-route53 (~> 1) 1015 | aws-sdk-route53domains (~> 1) 1016 | aws-sdk-route53recoverycluster (~> 1) 1017 | aws-sdk-route53recoverycontrolconfig (~> 1) 1018 | aws-sdk-route53recoveryreadiness (~> 1) 1019 | aws-sdk-route53resolver (~> 1) 1020 | aws-sdk-s3 (~> 1) 1021 | aws-sdk-s3control (~> 1) 1022 | aws-sdk-s3outposts (~> 1) 1023 | aws-sdk-sagemaker (~> 1) 1024 | aws-sdk-sagemakeredgemanager (~> 1) 1025 | aws-sdk-sagemakerfeaturestoreruntime (~> 1) 1026 | aws-sdk-sagemakerruntime (~> 1) 1027 | aws-sdk-savingsplans (~> 1) 1028 | aws-sdk-schemas (~> 1) 1029 | aws-sdk-secretsmanager (~> 1) 1030 | aws-sdk-securityhub (~> 1) 1031 | aws-sdk-serverlessapplicationrepository (~> 1) 1032 | aws-sdk-servicecatalog (~> 1) 1033 | aws-sdk-servicediscovery (~> 1) 1034 | aws-sdk-servicequotas (~> 1) 1035 | aws-sdk-ses (~> 1) 1036 | aws-sdk-sesv2 (~> 1) 1037 | aws-sdk-shield (~> 1) 1038 | aws-sdk-signer (~> 1) 1039 | aws-sdk-simpledb (~> 1) 1040 | aws-sdk-sms (~> 1) 1041 | aws-sdk-snowball (~> 1) 1042 | aws-sdk-snowdevicemanagement (~> 1) 1043 | aws-sdk-sns (~> 1) 1044 | aws-sdk-sqs (~> 1) 1045 | aws-sdk-ssm (~> 1) 1046 | aws-sdk-ssmcontacts (~> 1) 1047 | aws-sdk-ssmincidents (~> 1) 1048 | aws-sdk-ssoadmin (~> 1) 1049 | aws-sdk-ssooidc (~> 1) 1050 | aws-sdk-states (~> 1) 1051 | aws-sdk-storagegateway (~> 1) 1052 | aws-sdk-support (~> 1) 1053 | aws-sdk-swf (~> 1) 1054 | aws-sdk-synthetics (~> 1) 1055 | aws-sdk-textract (~> 1) 1056 | aws-sdk-timestreamquery (~> 1) 1057 | aws-sdk-timestreamwrite (~> 1) 1058 | aws-sdk-transcribeservice (~> 1) 1059 | aws-sdk-transcribestreamingservice (~> 1) 1060 | aws-sdk-transfer (~> 1) 1061 | aws-sdk-translate (~> 1) 1062 | aws-sdk-voiceid (~> 1) 1063 | aws-sdk-waf (~> 1) 1064 | aws-sdk-wafregional (~> 1) 1065 | aws-sdk-wafv2 (~> 1) 1066 | aws-sdk-wellarchitected (~> 1) 1067 | aws-sdk-workdocs (~> 1) 1068 | aws-sdk-worklink (~> 1) 1069 | aws-sdk-workmail (~> 1) 1070 | aws-sdk-workmailmessageflow (~> 1) 1071 | aws-sdk-workspaces (~> 1) 1072 | aws-sdk-workspacesweb (~> 1) 1073 | aws-sdk-xray (~> 1) 1074 | aws-sdk-robomaker (1.51.0) 1075 | aws-sdk-core (~> 3, >= 3.127.0) 1076 | aws-sigv4 (~> 1.1) 1077 | aws-sdk-rolesanywhere (1.0.0) 1078 | aws-sdk-core (~> 3, >= 3.127.0) 1079 | aws-sigv4 (~> 1.1) 1080 | aws-sdk-route53 (1.63.0) 1081 | aws-sdk-core (~> 3, >= 3.127.0) 1082 | aws-sigv4 (~> 1.1) 1083 | aws-sdk-route53domains (1.40.0) 1084 | aws-sdk-core (~> 3, >= 3.127.0) 1085 | aws-sigv4 (~> 1.1) 1086 | aws-sdk-route53recoverycluster (1.11.0) 1087 | aws-sdk-core (~> 3, >= 3.127.0) 1088 | aws-sigv4 (~> 1.1) 1089 | aws-sdk-route53recoverycontrolconfig (1.10.0) 1090 | aws-sdk-core (~> 3, >= 3.127.0) 1091 | aws-sigv4 (~> 1.1) 1092 | aws-sdk-route53recoveryreadiness (1.10.0) 1093 | aws-sdk-core (~> 3, >= 3.127.0) 1094 | aws-sigv4 (~> 1.1) 1095 | aws-sdk-route53resolver (1.37.0) 1096 | aws-sdk-core (~> 3, >= 3.127.0) 1097 | aws-sigv4 (~> 1.1) 1098 | aws-sdk-s3 (1.114.0) 1099 | aws-sdk-core (~> 3, >= 3.127.0) 1100 | aws-sdk-kms (~> 1) 1101 | aws-sigv4 (~> 1.4) 1102 | aws-sdk-s3control (1.50.0) 1103 | aws-sdk-core (~> 3, >= 3.127.0) 1104 | aws-sigv4 (~> 1.1) 1105 | aws-sdk-s3outposts (1.13.0) 1106 | aws-sdk-core (~> 3, >= 3.127.0) 1107 | aws-sigv4 (~> 1.1) 1108 | aws-sdk-sagemaker (1.130.0) 1109 | aws-sdk-core (~> 3, >= 3.127.0) 1110 | aws-sigv4 (~> 1.1) 1111 | aws-sdk-sagemakeredgemanager (1.11.0) 1112 | aws-sdk-core (~> 3, >= 3.127.0) 1113 | aws-sigv4 (~> 1.1) 1114 | aws-sdk-sagemakerfeaturestoreruntime (1.12.0) 1115 | aws-sdk-core (~> 3, >= 3.127.0) 1116 | aws-sigv4 (~> 1.1) 1117 | aws-sdk-sagemakerruntime (1.42.0) 1118 | aws-sdk-core (~> 3, >= 3.127.0) 1119 | aws-sigv4 (~> 1.1) 1120 | aws-sdk-savingsplans (1.26.0) 1121 | aws-sdk-core (~> 3, >= 3.127.0) 1122 | aws-sigv4 (~> 1.1) 1123 | aws-sdk-schemas (1.23.0) 1124 | aws-sdk-core (~> 3, >= 3.127.0) 1125 | aws-sigv4 (~> 1.1) 1126 | aws-sdk-secretsmanager (1.64.0) 1127 | aws-sdk-core (~> 3, >= 3.127.0) 1128 | aws-sigv4 (~> 1.1) 1129 | aws-sdk-securityhub (1.67.0) 1130 | aws-sdk-core (~> 3, >= 3.127.0) 1131 | aws-sigv4 (~> 1.1) 1132 | aws-sdk-serverlessapplicationrepository (1.43.0) 1133 | aws-sdk-core (~> 3, >= 3.127.0) 1134 | aws-sigv4 (~> 1.1) 1135 | aws-sdk-servicecatalog (1.71.0) 1136 | aws-sdk-core (~> 3, >= 3.127.0) 1137 | aws-sigv4 (~> 1.1) 1138 | aws-sdk-servicediscovery (1.46.0) 1139 | aws-sdk-core (~> 3, >= 3.127.0) 1140 | aws-sigv4 (~> 1.1) 1141 | aws-sdk-servicequotas (1.23.0) 1142 | aws-sdk-core (~> 3, >= 3.127.0) 1143 | aws-sigv4 (~> 1.1) 1144 | aws-sdk-ses (1.47.0) 1145 | aws-sdk-core (~> 3, >= 3.127.0) 1146 | aws-sigv4 (~> 1.1) 1147 | aws-sdk-sesv2 (1.27.0) 1148 | aws-sdk-core (~> 3, >= 3.127.0) 1149 | aws-sigv4 (~> 1.1) 1150 | aws-sdk-shield (1.48.0) 1151 | aws-sdk-core (~> 3, >= 3.127.0) 1152 | aws-sigv4 (~> 1.1) 1153 | aws-sdk-signer (1.38.0) 1154 | aws-sdk-core (~> 3, >= 3.127.0) 1155 | aws-sigv4 (~> 1.1) 1156 | aws-sdk-simpledb (1.35.0) 1157 | aws-sdk-core (~> 3, >= 3.127.0) 1158 | aws-sigv2 (~> 1.0) 1159 | aws-sdk-sms (1.40.0) 1160 | aws-sdk-core (~> 3, >= 3.127.0) 1161 | aws-sigv4 (~> 1.1) 1162 | aws-sdk-snowball (1.49.0) 1163 | aws-sdk-core (~> 3, >= 3.127.0) 1164 | aws-sigv4 (~> 1.1) 1165 | aws-sdk-snowdevicemanagement (1.7.0) 1166 | aws-sdk-core (~> 3, >= 3.127.0) 1167 | aws-sigv4 (~> 1.1) 1168 | aws-sdk-sns (1.53.0) 1169 | aws-sdk-core (~> 3, >= 3.127.0) 1170 | aws-sigv4 (~> 1.1) 1171 | aws-sdk-sqs (1.51.1) 1172 | aws-sdk-core (~> 3, >= 3.127.0) 1173 | aws-sigv4 (~> 1.1) 1174 | aws-sdk-ssm (1.137.0) 1175 | aws-sdk-core (~> 3, >= 3.127.0) 1176 | aws-sigv4 (~> 1.1) 1177 | aws-sdk-ssmcontacts (1.14.0) 1178 | aws-sdk-core (~> 3, >= 3.127.0) 1179 | aws-sigv4 (~> 1.1) 1180 | aws-sdk-ssmincidents (1.15.0) 1181 | aws-sdk-core (~> 3, >= 3.127.0) 1182 | aws-sigv4 (~> 1.1) 1183 | aws-sdk-ssoadmin (1.16.0) 1184 | aws-sdk-core (~> 3, >= 3.127.0) 1185 | aws-sigv4 (~> 1.1) 1186 | aws-sdk-ssooidc (1.19.0) 1187 | aws-sdk-core (~> 3, >= 3.127.0) 1188 | aws-sigv4 (~> 1.1) 1189 | aws-sdk-states (1.48.0) 1190 | aws-sdk-core (~> 3, >= 3.127.0) 1191 | aws-sigv4 (~> 1.1) 1192 | aws-sdk-storagegateway (1.68.0) 1193 | aws-sdk-core (~> 3, >= 3.127.0) 1194 | aws-sigv4 (~> 1.1) 1195 | aws-sdk-support (1.41.0) 1196 | aws-sdk-core (~> 3, >= 3.127.0) 1197 | aws-sigv4 (~> 1.1) 1198 | aws-sdk-swf (1.36.0) 1199 | aws-sdk-core (~> 3, >= 3.127.0) 1200 | aws-sigv4 (~> 1.1) 1201 | aws-sdk-synthetics (1.28.0) 1202 | aws-sdk-core (~> 3, >= 3.127.0) 1203 | aws-sigv4 (~> 1.1) 1204 | aws-sdk-textract (1.38.0) 1205 | aws-sdk-core (~> 3, >= 3.127.0) 1206 | aws-sigv4 (~> 1.1) 1207 | aws-sdk-timestreamquery (1.16.0) 1208 | aws-sdk-core (~> 3, >= 3.127.0) 1209 | aws-sigv4 (~> 1.1) 1210 | aws-sdk-timestreamwrite (1.14.0) 1211 | aws-sdk-core (~> 3, >= 3.127.0) 1212 | aws-sigv4 (~> 1.1) 1213 | aws-sdk-transcribeservice (1.75.0) 1214 | aws-sdk-core (~> 3, >= 3.127.0) 1215 | aws-sigv4 (~> 1.1) 1216 | aws-sdk-transcribestreamingservice (1.42.0) 1217 | aws-sdk-core (~> 3, >= 3.127.0) 1218 | aws-sigv4 (~> 1.1) 1219 | aws-sdk-transfer (1.56.0) 1220 | aws-sdk-core (~> 3, >= 3.127.0) 1221 | aws-sigv4 (~> 1.1) 1222 | aws-sdk-translate (1.45.0) 1223 | aws-sdk-core (~> 3, >= 3.127.0) 1224 | aws-sigv4 (~> 1.1) 1225 | aws-sdk-voiceid (1.8.0) 1226 | aws-sdk-core (~> 3, >= 3.127.0) 1227 | aws-sigv4 (~> 1.1) 1228 | aws-sdk-waf (1.47.0) 1229 | aws-sdk-core (~> 3, >= 3.127.0) 1230 | aws-sigv4 (~> 1.1) 1231 | aws-sdk-wafregional (1.48.0) 1232 | aws-sdk-core (~> 3, >= 3.127.0) 1233 | aws-sigv4 (~> 1.1) 1234 | aws-sdk-wafv2 (1.39.0) 1235 | aws-sdk-core (~> 3, >= 3.127.0) 1236 | aws-sigv4 (~> 1.1) 1237 | aws-sdk-wellarchitected (1.17.0) 1238 | aws-sdk-core (~> 3, >= 3.127.0) 1239 | aws-sigv4 (~> 1.1) 1240 | aws-sdk-workdocs (1.39.0) 1241 | aws-sdk-core (~> 3, >= 3.127.0) 1242 | aws-sigv4 (~> 1.1) 1243 | aws-sdk-worklink (1.33.0) 1244 | aws-sdk-core (~> 3, >= 3.127.0) 1245 | aws-sigv4 (~> 1.1) 1246 | aws-sdk-workmail (1.50.0) 1247 | aws-sdk-core (~> 3, >= 3.127.0) 1248 | aws-sigv4 (~> 1.1) 1249 | aws-sdk-workmailmessageflow (1.21.0) 1250 | aws-sdk-core (~> 3, >= 3.127.0) 1251 | aws-sigv4 (~> 1.1) 1252 | aws-sdk-workspaces (1.69.0) 1253 | aws-sdk-core (~> 3, >= 3.127.0) 1254 | aws-sigv4 (~> 1.1) 1255 | aws-sdk-workspacesweb (1.4.0) 1256 | aws-sdk-core (~> 3, >= 3.127.0) 1257 | aws-sigv4 (~> 1.1) 1258 | aws-sdk-xray (1.47.0) 1259 | aws-sdk-core (~> 3, >= 3.127.0) 1260 | aws-sigv4 (~> 1.1) 1261 | aws-sigv2 (1.1.0) 1262 | aws-sigv4 (1.5.0) 1263 | aws-eventstream (~> 1, >= 1.0.2) 1264 | aws_config (0.1.0) 1265 | awsecrets (1.15.1) 1266 | aws-sdk (>= 2, < 4) 1267 | aws_config (~> 0.1.0) 1268 | awspec (1.0.0) 1269 | aws-sdk (~> 3) 1270 | awsecrets (~> 1) 1271 | dry-inflector 1272 | ipaddress 1273 | rspec (~> 3.0) 1274 | rspec-its 1275 | term-ansicolor 1276 | thor 1277 | concurrent-ruby (1.2.2) 1278 | diff-lcs (1.5.0) 1279 | dnsruby (1.61.9) 1280 | simpleidn (~> 0.1) 1281 | dry-inflector (0.3.0) 1282 | i18n (1.12.0) 1283 | concurrent-ruby (~> 1.0) 1284 | ipaddress (0.8.3) 1285 | jmespath (1.6.1) 1286 | minitest (5.16.2) 1287 | parallel (1.22.1) 1288 | parser (3.1.2.0) 1289 | ast (~> 2.4.1) 1290 | rack (3.0.6.1) 1291 | rainbow (3.1.1) 1292 | rake (12.3.3) 1293 | regexp_parser (2.5.0) 1294 | rexml (3.2.5) 1295 | rspec (3.11.0) 1296 | rspec-core (~> 3.11.0) 1297 | rspec-expectations (~> 3.11.0) 1298 | rspec-mocks (~> 3.11.0) 1299 | rspec-core (3.11.0) 1300 | rspec-support (~> 3.11.0) 1301 | rspec-expectations (3.11.0) 1302 | diff-lcs (>= 1.2.0, < 2.0) 1303 | rspec-support (~> 3.11.0) 1304 | rspec-its (1.3.0) 1305 | rspec-core (>= 3.0.0) 1306 | rspec-expectations (>= 3.0.0) 1307 | rspec-mocks (3.11.1) 1308 | diff-lcs (>= 1.2.0, < 2.0) 1309 | rspec-support (~> 3.11.0) 1310 | rspec-support (3.11.0) 1311 | rubocop (1.30.1) 1312 | parallel (~> 1.10) 1313 | parser (>= 3.1.0.0) 1314 | rainbow (>= 2.2.2, < 4.0) 1315 | regexp_parser (>= 1.8, < 3.0) 1316 | rexml (>= 3.2.5, < 4.0) 1317 | rubocop-ast (>= 1.18.0, < 2.0) 1318 | ruby-progressbar (~> 1.7) 1319 | unicode-display_width (>= 1.4.0, < 3.0) 1320 | rubocop-ast (1.18.0) 1321 | parser (>= 3.1.1.0) 1322 | rubocop-govuk (4.5.0) 1323 | rubocop (= 1.30.1) 1324 | rubocop-ast (= 1.18.0) 1325 | rubocop-rails (= 2.14.2) 1326 | rubocop-rake (= 0.6.0) 1327 | rubocop-rspec (= 2.11.1) 1328 | rubocop-rails (2.14.2) 1329 | activesupport (>= 4.2.0) 1330 | rack (>= 1.1) 1331 | rubocop (>= 1.7.0, < 2.0) 1332 | rubocop-rake (0.6.0) 1333 | rubocop (~> 1.0) 1334 | rubocop-rspec (2.11.1) 1335 | rubocop (~> 1.19) 1336 | ruby-progressbar (1.11.0) 1337 | simpleidn (0.2.1) 1338 | unf (~> 0.1.4) 1339 | sync (0.5.0) 1340 | term-ansicolor (1.7.1) 1341 | tins (~> 1.0) 1342 | thor (1.2.1) 1343 | tins (1.31.1) 1344 | sync 1345 | tzinfo (2.0.6) 1346 | concurrent-ruby (~> 1.0) 1347 | unf (0.1.4) 1348 | unf_ext 1349 | unf_ext (0.0.8.2) 1350 | unicode-display_width (2.2.0) 1351 | 1352 | PLATFORMS 1353 | ruby 1354 | 1355 | DEPENDENCIES 1356 | awspec (~> 1.0.0) 1357 | dnsruby 1358 | minitest (~> 5.0) 1359 | rake (~> 12.3) 1360 | rspec 1361 | rubocop-govuk 1362 | 1363 | BUNDLED WITH 1364 | 2.4.1 1365 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Crown Copyright (Government Digital Service) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GOV.UK DNS # 2 | 3 | Historically used to manage DNS for a number of subdomains of gov.uk 4 | 5 | Now superseded by https://github.com/alphagov/govuk-dns-tf (which is a private repository as it contains possibly sensitive zone information). 6 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "rspec/core/rake_task" 2 | require_relative "./lib/tasks/utilities/common" 3 | 4 | # Normal tests are anything not tagged 'validate_dns' 5 | RSpec::Core::RakeTask.new(:rspec) do |t| 6 | t.rspec_opts = ["--tag", "~validate_dns"] 7 | end 8 | 9 | desc "RuboCop" 10 | task :lint, :environment do 11 | sh "bundle exec rubocop --format clang" 12 | end 13 | 14 | task default: %i[lint rspec] 15 | 16 | RSpec::Core::RakeTask.new(:validate_dns) do |t| 17 | unless File.exist?(zonefile) 18 | abort("Zonefile, #{zonefile}, not found.") 19 | end 20 | t.rspec_opts = ["--tag", "validate_dns"] 21 | end 22 | 23 | FileList["lib/tasks/*.rake"].each do |rake_file| 24 | import rake_file 25 | end 26 | -------------------------------------------------------------------------------- /jenkins.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | case "$1" in 6 | plan) 7 | CMD='tf:plan' 8 | ;; 9 | apply) 10 | CMD='tf:apply' 11 | ;; 12 | *) 13 | echo "Didn't recognise argument: must be plan or apply" 14 | exit 1 15 | esac 16 | 17 | git clone 'git@github.com:alphagov/govuk-dns-config.git' 18 | 19 | cp govuk-dns-config/$ZONEFILE . 20 | 21 | bundle install --path "${HOME}/bundles/${JOB_NAME}" 22 | bundle exec rake validate_yaml 23 | bundle exec rake generate_terraform 24 | bundle exec rake ${CMD} 25 | -------------------------------------------------------------------------------- /lib/tasks/generate_terraform.rake: -------------------------------------------------------------------------------- 1 | require "fileutils" 2 | require "yaml" 3 | require "json" 4 | 5 | require_relative "./utilities/common" 6 | require_relative "./utilities/generate" 7 | 8 | desc "Generate Terraform DNS configuration" 9 | task :generate_terraform do 10 | Dir.mkdir(TMP_DIR) unless File.exist?(TMP_DIR) 11 | 12 | # Clean the tmp-dir 13 | files = Dir["./#{TMP_DIR}/*/*.tf.json"] 14 | files << Dir["./#{TMP_DIR}/*.tf.json"] 15 | unless files.empty? 16 | FileUtils.rm files 17 | end 18 | 19 | # Load configuration 20 | config_file = YAML.load_file(zonefile) 21 | origin = config_file["origin"] 22 | deployment = config_file["deployment"] 23 | records = config_file["records"] 24 | 25 | abort("Origin does not have trailing dot") unless origin.match?(/\.$/) 26 | 27 | # Render all the expected files 28 | providers.each do |current_provider| 29 | abort("Must set deployment options in configuration file") if deployment[current_provider].empty? 30 | 31 | deploy_vars = deployment[current_provider] 32 | 33 | out = generate_terraform_object(statefile_name, region, deploy_env, current_provider, origin, records, deploy_vars) 34 | 35 | provider_dir = "#{TMP_DIR}/#{current_provider}" 36 | Dir.mkdir(provider_dir) unless File.exist?(provider_dir) 37 | # Use pretty_generate so the JSON is still vaguely human readable 38 | File.write("#{provider_dir}/zone.tf.json", JSON.pretty_generate(out)) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/tasks/terraform.rake: -------------------------------------------------------------------------------- 1 | require_relative "./utilities/common" 2 | 3 | namespace :tf do 4 | desc "Validate the generated terraform" 5 | task :validate do 6 | _check_terraform_version 7 | providers.each do |current_provider| 8 | puts "Validating #{current_provider} terraform" 9 | _run_system_command("terraform validate #{TMP_DIR}/#{current_provider}") 10 | end 11 | end 12 | 13 | desc "Apply the terraform resources" 14 | task :apply do 15 | _run_terraform_init 16 | _run_terraform_cmd_for_providers("apply -auto-approve") 17 | end 18 | 19 | desc "Destroy the terraform resources" 20 | task :destroy do 21 | _run_terraform_cmd_for_providers("destroy") 22 | end 23 | 24 | desc "Show the plan" 25 | task :plan do 26 | _run_terraform_init 27 | _run_terraform_cmd_for_providers("plan") 28 | end 29 | end 30 | 31 | def _run_terraform_init 32 | providers.each do |current_provider| 33 | _run_system_command("cd #{TMP_DIR}/#{current_provider} && terraform init -reconfigure -get=true") 34 | end 35 | end 36 | 37 | def _run_terraform_cmd_for_providers(command) 38 | _check_terraform_version 39 | _local_state_check 40 | _validate_terraform_environment 41 | _purge_remote_state 42 | 43 | puts command 44 | 45 | providers.each do |current_provider| 46 | puts "Running for #{current_provider}" 47 | 48 | puts "Using statefile: s3://#{bucket_name}/#{current_provider}/#{statefile_name}" 49 | 50 | terraform_cmd = [] 51 | terraform_cmd << "cd #{TMP_DIR}/#{current_provider} &&" 52 | terraform_cmd << "terraform" 53 | terraform_cmd << command 54 | 55 | _run_system_command(terraform_cmd.join(" ")) 56 | end 57 | end 58 | 59 | def _local_state_check 60 | state_file = "terraform.tfstate" 61 | 62 | if File.exist? state_file 63 | abort("Local state file should not exist. We use remote state files.") 64 | end 65 | end 66 | 67 | def _purge_remote_state 68 | state_file = ".terraform/terraform.tfstate" 69 | 70 | FileUtils.rm state_file if File.exist? state_file 71 | 72 | if File.exist? state_file 73 | abort("State file should not exist: #{state_file}") 74 | end 75 | end 76 | 77 | def _validate_terraform_environment 78 | allowed_envs = %w[test staging integration production] 79 | 80 | unless allowed_envs.include?(deploy_env) 81 | abort("Please set 'DEPLOY_ENV' environment variable to one of #{allowed_envs.join(', ')}") 82 | end 83 | 84 | ENV["AWS_DEFAULT_REGION"] = ENV["AWS_DEFAULT_REGION"] || region 85 | 86 | providers.each do |current_provider| 87 | required_vars = REQUIRED_ENV_VARS[current_provider.to_sym] 88 | required_vars[:env].each do |var| 89 | required_from_env(var) 90 | end 91 | end 92 | end 93 | 94 | def _check_terraform_version 95 | # Make sure that the version of Terraform we're using is new enough 96 | current_terraform_version = Gem::Version.new(`terraform version`.split("\n").first.split(" ")[1].delete("v")) 97 | minimum_terraform_version = Gem::Version.new(File.read(".terraform-version").strip) 98 | maximum_terraform_version = minimum_terraform_version.bump 99 | 100 | if current_terraform_version < minimum_terraform_version 101 | puts "Terraform is not up to date enough." 102 | puts "v#{current_terraform_version} installed, v#{minimum_terraform_version} required." 103 | exit 1 104 | elsif current_terraform_version > maximum_terraform_version 105 | puts "Terraform is too new." 106 | puts "We do not support terraform #{maximum_terraform_version} and above" 107 | exit 1 108 | end 109 | end 110 | 111 | def _run_system_command(command) 112 | if ENV["VERBOSE"] 113 | puts command.to_s 114 | end 115 | 116 | abort("#{command} failed") unless system(command) 117 | end 118 | -------------------------------------------------------------------------------- /lib/tasks/utilities/common.rb: -------------------------------------------------------------------------------- 1 | require "yaml" 2 | 3 | TMP_DIR = "tf-tmp".freeze 4 | 5 | REQUIRED_ENV_VARS = { 6 | gcp: { 7 | env: %w[GOOGLE_OAUTH_ACCESS_TOKEN GOOGLE_REGION GOOGLE_PROJECT].freeze, 8 | }.freeze, 9 | aws: { 10 | env: %w[AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_DEFAULT_REGION].freeze, 11 | }.freeze, 12 | }.freeze 13 | 14 | ALLOWED_PROVIDERS = REQUIRED_ENV_VARS.keys.map(&:to_s).freeze 15 | 16 | def required_from_env(var, msg = "Please set the '#{var}' environment variable.") 17 | unless ENV.include?(var) 18 | abort(msg) 19 | end 20 | ENV[var] 21 | end 22 | 23 | def statefile_name 24 | if ENV["ZONEFILE"].nil? 25 | "terraform.tfstate" 26 | else 27 | # Statefile called publishing-service-gov-uk.tfstate 28 | filename = ENV["ZONEFILE"].split("/")[-1] 29 | "#{filename.gsub('.yaml', '').tr('.', '-')}.tfstate" 30 | end 31 | end 32 | 33 | def deploy_env 34 | required_from_env("DEPLOY_ENV") 35 | end 36 | 37 | def zonefile 38 | required_from_env("ZONEFILE") 39 | end 40 | 41 | def region 42 | ENV["REGION"] || "eu-west-1" 43 | end 44 | 45 | def bucket_name 46 | ENV["BUCKET_NAME"] || "dns-state-bucket-#{deploy_env}" 47 | end 48 | 49 | def providers 50 | unless ENV["PROVIDERS"].nil? 51 | return [ENV["PROVIDERS"]] 52 | end 53 | 54 | abort("Please set the 'PROVIDERS' environment variable to one of: #{ALLOWED_PROVIDERS.join(', ')}") 55 | end 56 | -------------------------------------------------------------------------------- /lib/tasks/utilities/generate.rb: -------------------------------------------------------------------------------- 1 | require "digest" 2 | 3 | def generate_terraform_object(statefile_name, region, deploy_env, provider, origin, records, deployment_config) 4 | case provider 5 | when "gcp" 6 | tf_provider = "google" 7 | tf_provider_source = "hashicorp/google" 8 | 9 | resources = { google_dns_record_set: _get_gcp_resource(records, origin, deployment_config) } 10 | when "aws" 11 | tf_provider = "aws" 12 | tf_provider_source = "hashicorp/aws" 13 | 14 | resources = { aws_route53_record: _get_aws_resource(records, deployment_config) } 15 | end 16 | { 17 | terraform: { 18 | backend: { 19 | "s3": { 20 | encrypt: true, 21 | bucket: "dns-state-bucket-#{deploy_env}", 22 | key: "#{provider}/#{statefile_name}", 23 | region:, 24 | }, 25 | }, 26 | required_version: "= 1.4.1", 27 | required_providers: { 28 | "#{tf_provider}": { 29 | version: provider_version(tf_provider), 30 | source: tf_provider_source, 31 | }, 32 | }, 33 | }, 34 | provider: { 35 | "#{tf_provider}": { 36 | region: "eu-west-1", 37 | }, 38 | }, 39 | resource: resources, 40 | } 41 | end 42 | 43 | def provider_version(tf_provider) 44 | case tf_provider 45 | when "google" 46 | "4.57.0" 47 | when "aws" 48 | "4.58.0" 49 | end 50 | end 51 | 52 | def _get_gcp_resource(records, origin, deployment_config) 53 | resource_hash = {} 54 | 55 | grouped_records = records.group_by { |rec| [rec["subdomain"], rec["record_type"]] } 56 | 57 | grouped_records.each do |subdomain_and_type, record_set| 58 | subdomain, type = subdomain_and_type 59 | data = record_set.collect { |r| _split_line_gcp(r["data"]) }.flatten 60 | title = _get_resource_title subdomain, type 61 | 62 | record_name = subdomain == "@" ? origin : "#{subdomain}.#{origin}" 63 | 64 | resource_hash[title] = { 65 | managed_zone: deployment_config["zone_name"], 66 | name: record_name, 67 | type:, 68 | ttl: record_set.collect { |r| r["ttl"] }.min, 69 | rrdatas: data, 70 | } 71 | end 72 | 73 | resource_hash 74 | end 75 | 76 | def _get_aws_resource(records, deployment_config) 77 | resource_hash = {} 78 | 79 | grouped_records = records.group_by { |rec| [rec["subdomain"], rec["record_type"]] } 80 | 81 | grouped_records.each do |subdomain_and_type, record_set| 82 | subdomain, type = subdomain_and_type 83 | data = record_set.collect { |r| _split_line_aws(r["data"]) }.flatten 84 | title = _get_resource_title subdomain, type 85 | 86 | record_name = subdomain == "@" ? "" : subdomain.to_s 87 | 88 | resource_hash[title] = { 89 | zone_id: deployment_config["zone_id"], 90 | name: record_name, 91 | type:, 92 | ttl: record_set.collect { |r| r["ttl"] }.min, 93 | records: data, 94 | } 95 | end 96 | 97 | resource_hash 98 | end 99 | 100 | def _split_line_gcp(data) 101 | if data.include?("v=DMARC1") && (data.length > 254) 102 | data1 = data.delete(" ") 103 | data1.split(";").join("; ").split(",").join(", ") 104 | else 105 | data.scan(/.{1,255}/).join(" ") 106 | end 107 | end 108 | 109 | def _split_line_aws(data) 110 | if data.include?("v=DMARC1") && (data.length > 254) 111 | data1 = data.delete(" ") 112 | data1.split(";").join(';""').split(",").join(',""') 113 | else 114 | data.scan(/.{1,255}/).join('""') 115 | end 116 | end 117 | 118 | def _get_tf_safe_data(data) 119 | # Terraform requires escaped slashes in its strings. 120 | # The 6 '\'s are required because of how gsub works (see https://www.ruby-forum.com/topic/143645) 121 | data.gsub("\\", "\\\\\\") 122 | end 123 | 124 | def _get_resource_title(title, type) 125 | _get_tf_safe_title "#{type}_#{title}" 126 | end 127 | 128 | def _get_tf_safe_title(title) 129 | # Terraform resource records cannot contain '.'s or '@'s 130 | title.tr(".", "_").gsub(/@/, "AT").gsub("*", "WILDCARD") 131 | end 132 | -------------------------------------------------------------------------------- /lib/tasks/utilities/zone_file_field_validator.rb: -------------------------------------------------------------------------------- 1 | module ZoneFileFieldValidator 2 | MIN_TTL = 60 3 | MAX_TTL = 86_400 # 1 day 4 | 5 | def self.fqdn?(domainname) 6 | regex = %r{ 7 | \A # Match the start of the string 8 | [-a-z0-9_]+ # Match the first label made of numbers, letters, hyphens and underscores 9 | \. # Make sure we have at least a TLD 10 | [-.a-z0-9_]* # Other characters should be alphanumeric, periods, hyphens and underscores 11 | \. # Final character should be a period 12 | \z # Match the end of the string 13 | }x 14 | 15 | domainname&.match?(regex) 16 | end 17 | 18 | def self.ipv4?(address) 19 | regex = %r{ 20 | \A # Start of string 21 | (?: # Non-capturing group to match digits plus a period 22 | (?: 23 | 25[0-5]| # Match digits 250-255 24 | 2[0-4][0-9]| # Match digits 200-249 25 | [01]?[0-9][0-9]? # Match digits 0-199 26 | )\. # Match the period 27 | ){3} # Match three blocks of the blocks 28 | (?: 29 | 25[0-5]| # Match the final block 30 | 2[0-4][0-9]| 31 | [01]?[0-9][0-9]? 32 | ) 33 | \z # End of string 34 | }x 35 | 36 | address&.match?(regex) 37 | end 38 | 39 | def self.ipv6?(address) 40 | # Regex taken from https://home.deds.nl/~aeron/regex/ and simplified. We 41 | # don't need to support mixed IPv6/IPv4 adresses (e.g. ::1.2.3.4). 42 | regex = %r{ 43 | \A # Start of string 44 | ( # Capture group for a compressed field 45 | ( 46 | (?=.*(::)) # Lookahead for compressed fields 47 | (?!.*\3.+\3) # Lookbehind for more than one compression 48 | )\3?| # Match the compressed group, or... 49 | [\dA-F]{1,4}: # Match the first 16-bit hex value and trailing colon 50 | ) 51 | ( 52 | [\dA-F]{1,4} # Match a 16-bit hex value 53 | ( # Followed by... 54 | \3| # The double-colon from the compressed group, or... 55 | :\b| # A colon followed by a word boundary, or... 56 | $ # The end of the string 57 | )|\2 # Match the 16-bit hex value, or the compression capture 58 | ){7} # Match seven segments 59 | \z # End of string 60 | }xi 61 | 62 | address&.match?(regex) 63 | end 64 | 65 | def self.mx?(priority_and_domain) 66 | regex = %r{ 67 | \A # Start of string 68 | [0-9]* # Match any number of digits which make up the priority 69 | \s # Whitespace delineated fields 70 | (?.*) # Capture the domain field for further testing 71 | \z # End of string 72 | }x 73 | 74 | matches = regex.match(priority_and_domain) 75 | return false if matches.nil? 76 | 77 | fqdn?(matches["domain"]) && matches[0] == priority_and_domain 78 | end 79 | 80 | def self.subdomain?(subdomain) 81 | return false if subdomain == "" # Should not be blank, should be '@' 82 | return true if subdomain == "@" # Reference to $ORIGIN 83 | 84 | # Allowed characters are numbers, lower-case letters, periods, 85 | # hyphens and underscores per part. Wildcard character (*) is only 86 | # allowed on its own in the least significant part 87 | regex = /\A(\*\.)?[-_.a-z0-9]*\z|\A\*\z/ 88 | 89 | subdomain&.match?(regex) 90 | end 91 | 92 | def self.txt_subdomain?(subdomain) 93 | return false if subdomain == "" # Should not be blank, should be '@' 94 | return true if subdomain == "@" # Reference to $ORIGIN 95 | 96 | # TXT subdomains may contain underscores and upper case letters in 97 | # addition to other subdomain characters 98 | regex = /\A[-_.a-zA-Z0-9]*\z/ 99 | 100 | subdomain.match?(regex) 101 | end 102 | 103 | def self.txt_data_semicolons?(data) 104 | semicolons = data.scan(/;/).length 105 | esc_semicolons = data.scan(/(\\;)/).length 106 | 107 | if semicolons.positive? && (esc_semicolons < semicolons) 108 | false 109 | end 110 | end 111 | 112 | def self.ttl?(ttl) 113 | return false if /\A\d*\z/ !~ ttl # Not a valid integer string 114 | 115 | ttl = Integer(ttl) 116 | 117 | (MIN_TTL <= ttl) && (ttl <= MAX_TTL) # Check Bounds 118 | end 119 | 120 | def self.get_record_errors(record) 121 | errors = [] 122 | 123 | ttl = record["ttl"] 124 | data = record["data"] 125 | type = record["record_type"] 126 | subdomain = record["subdomain"] 127 | 128 | # TTL tests 129 | if ttl.nil? 130 | errors << "Missing 'ttl' field in record #{record}." 131 | elsif !ttl?(ttl) 132 | errors << "TTL must be an integer between #{MIN_TTL}s and #{MAX_TTL}s, got: '#{ttl}'." 133 | end 134 | 135 | # Basic data tests 136 | if data.nil? 137 | errors << "Missing 'data' field in record #{record}." 138 | end 139 | 140 | # Most of the validation for data relies on the record type 141 | case type 142 | when nil? 143 | errors << "Missing 'record_type' field in record #{record}." 144 | when "A" 145 | errors << "A record data field must be an IPv4 address, got: '#{data}'." unless ipv4?(data) 146 | when "AAAA" 147 | errors << "AAAA record data field must be an IPv6 address, got: '#{data}'." unless ipv6?(data) 148 | when "NS" 149 | errors << "NS record data field must be a lower-case FQDN (with a trailing dot), got: '#{data}'." unless fqdn?(data) 150 | when "MX" 151 | errors << "MX record data field must be of the form ' ', got: '#{data}'." unless mx?(data) 152 | when "TXT" 153 | errors << "TXT record data field must not be empty." if data.empty? 154 | errors << "TXT record data semicolons should be escaped, got: '#{data}'." unless txt_data_semicolons?(data).nil? 155 | when "CNAME" 156 | errors << "CNAME record data field must be a lower-case FQDN (with a trailing dot), got: '#{data}'." unless fqdn?(data) 157 | else 158 | errors << "Unknown record type: '#{type}'." 159 | end 160 | 161 | # Validation for subdomain only changes for TXT records 162 | if subdomain.nil? 163 | errors << "Missing 'subdomain' field in record #{record}." 164 | elsif type == "TXT" && !txt_subdomain?(subdomain) 165 | errors << "Invalid TXT subdomain field, must either be '@' or consist of numbers, lowercase letters, hyphens, periods and underscores; got: '#{subdomain}'." 166 | elsif type != "TXT" && !subdomain?(subdomain) 167 | errors << "Invalid #{type} subdomain field, must either be '@' or consist of numbers, lowercase letters, hyphens, periods, and wildcards; got: '#{subdomain}'." 168 | end 169 | 170 | errors 171 | end 172 | 173 | def self.get_zone_errors(zone_file) 174 | errors = [] 175 | 176 | origin = zone_file["origin"] 177 | records = zone_file["records"] 178 | 179 | if origin.nil? || origin.empty? 180 | errors << "Origin field must be set" 181 | elsif !fqdn?(origin) 182 | errors << "Origin must be a lower-case FQDN, got #{origin}" 183 | end 184 | 185 | if records.nil? || records.empty? 186 | errors << "No records found." 187 | else 188 | records.each do |rec| 189 | errors += get_record_errors(rec) 190 | end 191 | end 192 | 193 | errors 194 | end 195 | end 196 | -------------------------------------------------------------------------------- /lib/tasks/validate_yaml.rake: -------------------------------------------------------------------------------- 1 | require "yaml" 2 | require "optparse" 3 | 4 | require_relative "./utilities/common" 5 | require_relative "./utilities/zone_file_field_validator" 6 | 7 | desc "Validate a YAML Zone file" 8 | task :validate_yaml do 9 | zone_data = YAML.load_file(zonefile) 10 | 11 | errors = ZoneFileFieldValidator.get_zone_errors(zone_data) 12 | 13 | unless errors.empty? 14 | errors.each { |err| puts err } 15 | puts "\n#{errors.length} errors found." 16 | exit 1 17 | end 18 | 19 | puts "No errors found." if ENV["VERBOSE"] 20 | end 21 | 22 | desc "Validate all YAML files in a given directory" 23 | task :validate_all_yaml do 24 | dir = ENV["VALIDATE_DIR_YAML"] 25 | 26 | abort("Must set VALIDATE_DIR_YAML environment variable.") unless dir 27 | 28 | files = Dir["#{dir}/*.yaml"] 29 | 30 | abort("No YAML files found in #{dir}.") if files.empty? 31 | 32 | files.each do |file| 33 | puts "Testing #{file}" 34 | zone_data = YAML.load_file(file) 35 | errors = ZoneFileFieldValidator.get_zone_errors(zone_data) 36 | 37 | unless errors.empty? 38 | errors.each { |err| puts err } 39 | puts "\n#{errors.length} errors found in #{file}." 40 | exit 1 41 | end 42 | 43 | puts "No errors found." if ENV["VERBOSE"] 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/common_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "../lib/tasks/utilities/common" 2 | 3 | RSpec.describe "Common tasks" do 4 | describe "statefile_name" do 5 | it 'should return "terraform.tfstate" by default' do 6 | expect(statefile_name).to eq "terraform.tfstate" 7 | end 8 | 9 | it "should return munged ZONEFILE name if set" do 10 | ENV["ZONEFILE"] = "foo.bar.baz.yaml" 11 | expect(statefile_name).to eq "foo-bar-baz.tfstate" 12 | end 13 | 14 | it "should remove path fragments from the ZONEFILE name if set" do 15 | ENV["ZONEFILE"] = "some/path/foo.bar.baz.yaml" 16 | expect(statefile_name).to eq "foo-bar-baz.tfstate" 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/generate_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "../lib/tasks/utilities/generate" 2 | require_relative "../lib/tasks/utilities/common" 3 | 4 | RSpec.describe "generate" do 5 | describe "_get_tf_safe_title" do 6 | it 'should replace "@" with "AT"' do 7 | expect(_get_tf_safe_title("@")).to eq("AT") 8 | end 9 | 10 | it 'should replace "."s with "_"s' do 11 | expect(_get_tf_safe_title("foo.bar")).to eq("foo_bar") 12 | end 13 | 14 | it 'should replace "*"s with "WILDCARD"s' do 15 | expect(_get_tf_safe_title("*.bar")).to eq("WILDCARD_bar") 16 | end 17 | 18 | it "should not change other titles" do 19 | expect(_get_tf_safe_title("unchanged")).to eq("unchanged") 20 | end 21 | end 22 | 23 | describe "_get_tf_safe_data" do 24 | it 'should replace "\\" with "\\\\"' do 25 | expect(_get_tf_safe_data("\\")).to eq("\\\\") 26 | end 27 | 28 | it "should not affect other data" do 29 | expect(_get_tf_safe_data("unchanged")).to eq("unchanged") 30 | end 31 | end 32 | 33 | describe "_get_resource_title" do 34 | it "should produce a unique safe tf title" do 35 | expect(_get_resource_title("example", "NS")).to eq("NS_example") 36 | end 37 | 38 | it "should produce a unique safe tf title" do 39 | expect(_get_resource_title("@", "NS")).to eq("NS_AT") 40 | end 41 | end 42 | 43 | describe "_get_gcp_resource" do 44 | it "should produce an object which matches the gcp cloudDNS terraform resource" do 45 | origin = "my.dnsname.com." 46 | deployment = { 47 | "zone_name" => "my-google-zone", 48 | } 49 | records = [ 50 | { 51 | "record_type" => "NS", 52 | "subdomain" => "test", 53 | "ttl" => "86400", 54 | "data" => "example.com.", 55 | }, 56 | ] 57 | expect = { 58 | "NS_test" => { 59 | managed_zone: "my-google-zone", 60 | name: "test.my.dnsname.com.", 61 | type: "NS", 62 | ttl: "86400", 63 | rrdatas: ["example.com."], 64 | }, 65 | } 66 | 67 | expect(_get_gcp_resource(records, origin, deployment)).to eq(expect) 68 | end 69 | 70 | it "should split long data lines to a maximum of 255 characters with spaces" do 71 | origin = "my.dnsname.com." 72 | deployment = { 73 | "zone_name" => "my-google-zone", 74 | } 75 | data = "123456790" * 30 76 | records = [ 77 | { 78 | "record_type" => "TXT", 79 | "subdomain" => "test", 80 | "ttl" => "86400", 81 | "data" => data, 82 | }, 83 | ] 84 | rrdatas = ["123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123 456790123456790"] 85 | result = _get_gcp_resource(records, origin, deployment) 86 | test_id = result.keys.first 87 | expect(result[test_id][:rrdatas]).to eq(rrdatas) 88 | end 89 | 90 | it 'should not include the "@" in the name field' do 91 | origin = "my.dnsname.com." 92 | deployment = { 93 | "zone_name" => "my-google-zone", 94 | } 95 | records = [ 96 | { 97 | "record_type" => "NS", 98 | "subdomain" => "@", 99 | "ttl" => "86400", 100 | "data" => "example.com.", 101 | }, 102 | ] 103 | 104 | result = _get_gcp_resource(records, origin, deployment) 105 | expect(result["NS_AT"][:name]).to eq("my.dnsname.com.") 106 | end 107 | 108 | it "should group records by subdomain and type" do 109 | origin = "my.dnsname.com." 110 | deployment = { 111 | "zone_name" => "my-google-zone", 112 | } 113 | records = [ 114 | { 115 | "record_type" => "NS", 116 | "subdomain" => "test", 117 | "ttl" => "86400", 118 | "data" => "example.com.", 119 | }, 120 | { 121 | "record_type" => "NS", 122 | "subdomain" => "test", 123 | "ttl" => "86400", 124 | "data" => "example2.com.", 125 | }, 126 | ] 127 | expect = { 128 | "NS_test" => { 129 | managed_zone: "my-google-zone", 130 | name: "test.my.dnsname.com.", 131 | type: "NS", 132 | ttl: "86400", 133 | rrdatas: ["example.com.", "example2.com."], 134 | }, 135 | } 136 | 137 | expect(_get_gcp_resource(records, origin, deployment)).to eq(expect) 138 | end 139 | end 140 | 141 | describe "_get_aws_resource" do 142 | it "should produce an object which matches the route53 terraform resource" do 143 | deployment = { "zone_id" => "route53zoneid" } 144 | records = [ 145 | { 146 | "record_type" => "NS", 147 | "subdomain" => "@", 148 | "ttl" => "86400", 149 | "data" => "example.com.", 150 | }, 151 | ] 152 | expect = { 153 | "NS_AT" => { 154 | zone_id: "route53zoneid", 155 | name: "", 156 | type: "NS", 157 | ttl: "86400", 158 | records: ["example.com."], 159 | }, 160 | } 161 | 162 | expect(_get_aws_resource(records, deployment)).to eq(expect) 163 | end 164 | 165 | it "should split long data lines to a maximum of 255 characters with escaped quotes" do 166 | deployment = { "zone_id" => "route53zoneid" } 167 | data = "123456790" * 30 168 | records = [ 169 | { 170 | "record_type" => "TXT", 171 | "subdomain" => "test", 172 | "ttl" => "86400", 173 | "data" => data, 174 | }, 175 | ] 176 | expected = ["123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123456790123\"\"456790123456790"] 177 | result = _get_aws_resource(records, deployment) 178 | test_id = result.keys.first 179 | expect(result[test_id][:records]).to eq(expected) 180 | end 181 | 182 | it 'should not include the "@" in the name field' do 183 | deployment = { "zone_id" => "route53zoneid" } 184 | records = [ 185 | { 186 | "record_type" => "NS", 187 | "subdomain" => "@", 188 | "ttl" => "86400", 189 | "data" => "example.com.", 190 | }, 191 | ] 192 | 193 | result = _get_aws_resource(records, deployment) 194 | expect(result["NS_AT"][:name]).to eq("") 195 | end 196 | 197 | it "should group records by subdomain and type" do 198 | deployment = { "zone_id" => "route53zoneid" } 199 | records = [ 200 | { 201 | "record_type" => "NS", 202 | "subdomain" => "@", 203 | "ttl" => "86400", 204 | "data" => "example.com.", 205 | }, 206 | { 207 | "record_type" => "NS", 208 | "subdomain" => "@", 209 | "ttl" => "86400", 210 | "data" => "example2.com.", 211 | }, 212 | ] 213 | expect = { 214 | "NS_AT" => { 215 | zone_id: "route53zoneid", 216 | name: "", 217 | type: "NS", 218 | ttl: "86400", 219 | records: ["example.com.", "example2.com."], 220 | }, 221 | } 222 | 223 | expect(_get_aws_resource(records, deployment)).to eq(expect) 224 | end 225 | end 226 | 227 | describe "_generate_terraform_object" do 228 | it "should be side-effect free for all providers and contain the correct resource" do 229 | statefile_name = "test/example.tfvars" 230 | region = "eu-west-1" 231 | deploy_env = "test" 232 | 233 | records = [ 234 | { 235 | "record_type" => "NS", 236 | "subdomain" => "@", 237 | "ttl" => "86400", 238 | "data" => "ns1.example.com.", 239 | }.freeze, 240 | { 241 | "record_type" => "NS", 242 | "subdomain" => "@", 243 | "ttl" => "86400", 244 | "data" => "ns2.example.com.", 245 | }.freeze, 246 | { 247 | "record_type" => "TXT", 248 | "subdomain" => "sub.", 249 | "ttl" => "3600", 250 | "data" => "Some test", 251 | }.freeze, 252 | { 253 | "record_type" => "A", 254 | "subdomain" => "sub.", 255 | "ttl" => "3600", 256 | "data" => "123.233.10.1", 257 | }.freeze, 258 | ].freeze 259 | 260 | origin = "my.dnsname.com." 261 | deployment = { 262 | "gcp" => { 263 | "dns_name" => "my.dnsname.com.", 264 | }, 265 | "aws" => { 266 | "zone_id" => "route53zoneid", 267 | }, 268 | } 269 | 270 | expected_resource_names = { 271 | "gcp" => :google_dns_record_set, 272 | "aws" => :aws_route53_record, 273 | }.freeze 274 | 275 | ALLOWED_PROVIDERS.each do |current_provider| 276 | result = nil 277 | # Because the records are frozen this (should) error if they're modified 278 | expect { 279 | result = generate_terraform_object(statefile_name, region, deploy_env, current_provider, origin, records, deployment) 280 | }.to_not raise_error 281 | 282 | expect(result).to include(:resource) 283 | expect(result[:resource]).to include(expected_resource_names[current_provider]) 284 | end 285 | end 286 | end 287 | end 288 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 2 | 3 | HERE = __dir__ 4 | 5 | RSpec.configure do |config| 6 | # Defaults produced by 'rspec --init' 7 | config.expect_with :rspec do |expectations| 8 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 9 | end 10 | 11 | config.mock_with :rspec do |mocks| 12 | mocks.verify_partial_doubles = true 13 | end 14 | 15 | config.shared_context_metadata_behavior = :apply_to_host_groups 16 | config.filter_run_when_matching :focus 17 | config.disable_monkey_patching! 18 | 19 | if config.files_to_run.one? 20 | config.default_formatter = "doc" 21 | end 22 | 23 | config.profile_examples = 10 24 | config.order = :random 25 | Kernel.srand config.seed 26 | end 27 | -------------------------------------------------------------------------------- /spec/validate_published_dns_spec.rb: -------------------------------------------------------------------------------- 1 | # 2 | # This spec compares the result of a DNS query with the contents of a YAML 3 | # DNS zone file. 4 | # 5 | # The spec will split up a set of tests on a per subdomain basis. Then for each 6 | # subdomain it will check that there are the expected number of records and 7 | # that each record has the correct type, data and a valid TTL (one that is less 8 | # than the maximum, set in the YAML). 9 | # 10 | # The YAML file is assumed to be one that passes the 'validate_yaml' task. 11 | # 12 | 13 | require "yaml" 14 | require "fileutils" 15 | require "dnsruby" 16 | 17 | DOMAIN_IGNORE_LIST = %w[gke.integration].freeze 18 | 19 | # We set a tag on these tests as we do not want to run them as part of the main 20 | # test suite. 21 | RSpec.describe "Validate the published DNS against the YAML.", validate_dns: true do 22 | zonefile = ENV["ZONEFILE"] 23 | # Exit early if no zonefile given, or it doesn't exist. 24 | break if zonefile.nil? || !File.exist?(zonefile) 25 | 26 | yaml_dns = YAML.load_file(zonefile) 27 | 28 | origin = yaml_dns["origin"] 29 | yaml_subdomains = yaml_dns["records"].group_by { |rec| rec["subdomain"] } 30 | 31 | custom_nameserver = ENV["CUSTOM_NS"] 32 | if custom_nameserver 33 | puts "Querying #{custom_nameserver}" 34 | resolver = Dnsruby::Resolver.new(nameserver: [custom_nameserver]) 35 | else 36 | resolver = Dnsruby::Resolver.new 37 | end 38 | 39 | yaml_subdomains.each do |subdomain, subdomain_records| 40 | describe "The '#{subdomain}' subdomain" do 41 | query = subdomain == "@" ? origin : "#{subdomain}.#{origin}" 42 | 43 | next if DOMAIN_IGNORE_LIST.include?(subdomain) 44 | 45 | # There are a couple of ways that the query can fail, this deals with the 46 | # two most likely: timeout and NXDomain. 47 | begin 48 | # Use 'ANY' to get all the records for the subdomain. 49 | records = resolver.query(query, "ANY") 50 | rescue Dnsruby::NXDomain 51 | # NXDomain is a test failure let RSpec know. 52 | it "should exist." do 53 | raise "NXDomain response, expected '#{subdomain}' to exist." 54 | end 55 | next 56 | rescue Dnsruby::ResolvTimeout 57 | # Timeout is not a test failure but we probably want to add a retry list 58 | # or similar for total validation. 59 | puts "Timeout getting response for '#{subdomain}'." 60 | next 61 | rescue Dnsruby::ServFail 62 | it "should not error." do 63 | raise "Dnsruby::ServFail response for '#{subdomain}'" 64 | end 65 | next 66 | end 67 | 68 | # The YAML does not include SOA records so remove those. 69 | answers = records.answer.reject { |ans| ans.type.to_s == "SOA" } 70 | 71 | # We do not manage NS records for the root domain 72 | answers = answers.reject { |ans| (subdomain == "@" && ans.type.to_s == "NS") } 73 | 74 | # If you query an authoritative nameserver then it may return a string of 75 | # related results until it provides the IP address. We're only interested 76 | # in the results for the specific record we are querying. 77 | answers = answers.select { |ans| ans.name.to_s == query.chomp(".") } 78 | 79 | it "should have the expected number of results." do 80 | expect(subdomain_records.length).to eq(answers.length), "expected #{subdomain_records.length} records, got: #{answers.length}." 81 | end 82 | 83 | answers.each do |ans| 84 | ans_type = ans.type.to_s 85 | ans_ttl = Integer(ans.ttl.to_s) 86 | 87 | it "should be a known record type." do 88 | expect(%w[A MX NS TXT CNAME]).to include(ans_type) 89 | end 90 | 91 | # DnsRuby doesn't provide a uniform way of getting the data field so 92 | # we have to parse it. 93 | ans_data = case ans_type 94 | when "TXT" 95 | ans.rdata[0].to_s.gsub(";", '\;').gsub(" ", '\\ ') 96 | when "MX" 97 | "#{ans.rdata[0]} #{ans.rdata[1]}." 98 | when "NS", "CNAME" 99 | # DnsRuby removes the trailing '.' from FQDNs but we need it. 100 | "#{ans.rdata}." 101 | when "A" 102 | ans.rdata.to_s 103 | end 104 | 105 | it "should have data" do 106 | expect(ans_data).to_not be_nil, "#{ans} should contain a data field." 107 | end 108 | 109 | # In theory we could roll all of our tests for the individual records 110 | # into some nested RSpec include/satisfy statements but by separately 111 | # finding the YAML record then testing against it we get better output. 112 | found = subdomain_records.select do |record| 113 | # TXT fields may be case sensitive but none of the other types we 114 | # currently check are (A, MX, NS, CNAME). 115 | if ans_type == "TXT" 116 | record["record_type"] == ans_type && 117 | record["data"] == ans_data 118 | elsif !record["data"].nil? && !ans_data.nil? 119 | record["record_type"] == ans_type && 120 | record["data"].casecmp(ans_data) == 0 121 | end 122 | end 123 | 124 | # We assume that we will not get duplicate records back from DNS. 125 | it "'#{ans_type}' record should be in the YAML with data: '#{ans_data}'." do 126 | expect(found.length).to be(1), "Expected to find 1 copy of '#{ans}' in the YAML." 127 | end 128 | next if found.length != 1 129 | 130 | it "should have a TTL less than #{found[0]['ttl']}s." do 131 | expect(ans_ttl).to be <= found[0]["ttl"].to_i 132 | end 133 | end 134 | end 135 | end 136 | end 137 | -------------------------------------------------------------------------------- /spec/validate_yaml_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "../lib/tasks/utilities/zone_file_field_validator" 2 | 3 | RSpec.describe "Zone file field validators" do 4 | describe "fqdn?", type: :class do 5 | it "should be true for trivial examples" do 6 | expect(ZoneFileFieldValidator.fqdn?("example.com.")).to be true 7 | end 8 | 9 | it "should be true for examples containing underscores" do 10 | expect(ZoneFileFieldValidator.fqdn?("_example.com.")).to be true 11 | end 12 | 13 | it "should be true for long examples" do 14 | expect(ZoneFileFieldValidator.fqdn?("this.is-a.surprisingly.long.example.1.com.")).to be true 15 | end 16 | 17 | it "should be false for examples lacking a TLD" do 18 | expect(ZoneFileFieldValidator.fqdn?("example.")).to be false 19 | end 20 | 21 | it "should be false for examples lacking a domain" do 22 | expect(ZoneFileFieldValidator.fqdn?(".com.")).to be false 23 | end 24 | 25 | it "should be false for examples containing semicolons" do 26 | expect(ZoneFileFieldValidator.fqdn?("bad;example.com.")).to be false 27 | end 28 | 29 | it "should be false for examples lacking a trailing period" do 30 | expect(ZoneFileFieldValidator.fqdn?("bad;example.com")).to be false 31 | end 32 | 33 | it "should be false for examples containing unicode" do 34 | expect(ZoneFileFieldValidator.fqdn?("båd_éxämple.com.")).to be false 35 | end 36 | 37 | it "should be false for examples containing uppercase" do 38 | expect(ZoneFileFieldValidator.fqdn?("BAD_EXAMPLE.COM.")).to be false 39 | end 40 | end 41 | 42 | describe "ipv4?" do 43 | it "should be true for trivial examples" do 44 | expect(ZoneFileFieldValidator.ipv4?("127.0.0.1")).to be true 45 | end 46 | 47 | it 'should be true for the "minimum" value' do 48 | expect(ZoneFileFieldValidator.ipv4?("0.0.0.0")).to be true 49 | end 50 | 51 | it 'should be true for the "maximum" value' do 52 | expect(ZoneFileFieldValidator.ipv4?("255.255.255.255")).to be true 53 | end 54 | 55 | it "should be false for values outside the maximum" do 56 | expect(ZoneFileFieldValidator.ipv4?("256.256.256.256")).to be false 57 | end 58 | 59 | it "should be false for values with fewer than 4 blocks" do 60 | expect(ZoneFileFieldValidator.ipv4?("1.1.1")).to be false 61 | end 62 | 63 | it "should be false for values with more than 4 blocks" do 64 | expect(ZoneFileFieldValidator.ipv4?("1.1.1.1.1")).to be false 65 | end 66 | 67 | it "should be false for values that contain letters" do 68 | expect(ZoneFileFieldValidator.ipv4?("d.e.a.d")).to be false 69 | end 70 | end 71 | 72 | describe "ipv6?" do 73 | it "should be true for trivial examples" do 74 | expect(ZoneFileFieldValidator.ipv6?("1111:2222:3333:4444:5555:6666:7777:8888")).to be true 75 | end 76 | 77 | it 'should be true for the "minimum" value' do 78 | expect(ZoneFileFieldValidator.ipv6?("::1")).to be true 79 | end 80 | 81 | it "should be true for values with leading zeroes" do 82 | expect(ZoneFileFieldValidator.ipv6?("ff06:00c4:0000:0000:0000:0000:0000:00c3")).to be true 83 | end 84 | 85 | it "should be true for values without leading zeroes" do 86 | expect(ZoneFileFieldValidator.ipv6?("ff06:c4:0:0:0:0:0:c3")).to be true 87 | end 88 | 89 | it "should be true for values with compressed runs of zero fields" do 90 | expect(ZoneFileFieldValidator.ipv6?("ff06:c4::c3")).to be true 91 | end 92 | 93 | it "should be false for values with fewer than 8 blocks" do 94 | expect(ZoneFileFieldValidator.ipv6?("0:0:0:0:0:0:0")).to be false 95 | end 96 | 97 | it "should be false for values with more than 4 blocks" do 98 | expect(ZoneFileFieldValidator.ipv6?("0:0:0:0:0:0:0:0:0")).to be false 99 | end 100 | 101 | it "should be false for values that contain invalid hex characters" do 102 | expect(ZoneFileFieldValidator.ipv6?("a:b:c:d:e:f:g:h")).to be false 103 | end 104 | end 105 | 106 | describe "mx?" do 107 | it "should be true for trivial examples" do 108 | expect(ZoneFileFieldValidator.mx?("10 example.com.")).to be true 109 | end 110 | 111 | it "should be true for larger examples" do 112 | expect(ZoneFileFieldValidator.mx?("10412 longer.test-example.com.")).to be true 113 | end 114 | 115 | it 'should be false for strings not of the format " "' do 116 | expect(ZoneFileFieldValidator.mx?("10")).to be false 117 | expect(ZoneFileFieldValidator.mx?("example.com.")).to be false 118 | expect(ZoneFileFieldValidator.mx?("example.com. 10")).to be false 119 | end 120 | 121 | it "should be false for strings with an invalid fqdn" do 122 | expect(ZoneFileFieldValidator.mx?("10 .com.")).to be false 123 | expect(ZoneFileFieldValidator.mx?("10 0.0.0.0")).to be false 124 | expect(ZoneFileFieldValidator.mx?("10 foo;bar.com")).to be false 125 | expect(ZoneFileFieldValidator.mx?("10 foo;bar.com.")).to be false 126 | end 127 | end 128 | 129 | describe "subdomain?" do 130 | it 'should be false for blank rather than "@"' do 131 | expect(ZoneFileFieldValidator.subdomain?("")).to be false 132 | end 133 | 134 | it 'should be true for "@" (reference to $ORIGIN' do 135 | expect(ZoneFileFieldValidator.subdomain?("@")).to be true 136 | end 137 | 138 | it "should be true for any string of letters, numbers, periods and hyphens" do 139 | expect(ZoneFileFieldValidator.subdomain?("example")).to be true 140 | expect(ZoneFileFieldValidator.subdomain?("long-example66")).to be true 141 | expect(ZoneFileFieldValidator.subdomain?("dotted.example")).to be true 142 | end 143 | 144 | it "should be true for wildcards" do 145 | expect(ZoneFileFieldValidator.subdomain?("*")).to be true 146 | expect(ZoneFileFieldValidator.subdomain?("*.foo")).to be true 147 | expect(ZoneFileFieldValidator.subdomain?("*.foo.bar")).to be true 148 | end 149 | 150 | it "should be true for strings that contain underscores" do 151 | expect(ZoneFileFieldValidator.subdomain?("bad_example")).to be true 152 | end 153 | 154 | it "should be false for strings that contain @ in addition to other things" do 155 | expect(ZoneFileFieldValidator.subdomain?("b@d_example")).to be false 156 | end 157 | 158 | it "should be false for strings that contain uppercase" do 159 | expect(ZoneFileFieldValidator.subdomain?("BAD_EXAMPLE")).to be false 160 | end 161 | 162 | it "should be false for strings that contain asterisks" do 163 | expect(ZoneFileFieldValidator.subdomain?("something*")).to be false 164 | expect(ZoneFileFieldValidator.subdomain?("*something")).to be false 165 | expect(ZoneFileFieldValidator.subdomain?("some*thing")).to be false 166 | end 167 | 168 | it "should be false for wildcards anywhere but the least significant part" do 169 | expect(ZoneFileFieldValidator.subdomain?("some.*.thing")).to be false 170 | expect(ZoneFileFieldValidator.subdomain?("thing.*")).to be false 171 | end 172 | end 173 | 174 | describe "txt_subdomain?" do 175 | it 'should be false for blank rather than "@"' do 176 | expect(ZoneFileFieldValidator.txt_subdomain?("")).to be false 177 | end 178 | 179 | it 'should be true for "@" (reference to $ORIGIN' do 180 | expect(ZoneFileFieldValidator.txt_subdomain?("@")).to be true 181 | end 182 | 183 | it "should be true for any string of letters, numbers, periods, hyphens and underscores" do 184 | expect(ZoneFileFieldValidator.txt_subdomain?("example")).to be true 185 | expect(ZoneFileFieldValidator.txt_subdomain?("long-example66")).to be true 186 | expect(ZoneFileFieldValidator.txt_subdomain?("dotted.example")).to be true 187 | end 188 | 189 | it "should be true for strings that contain underscores" do 190 | expect(ZoneFileFieldValidator.txt_subdomain?("good_example")).to be true 191 | end 192 | 193 | it "should be false for strings that contain @ in addition to other things" do 194 | expect(ZoneFileFieldValidator.txt_subdomain?("b@d_example")).to be false 195 | end 196 | end 197 | 198 | describe "txt_data_semicolons?" do 199 | it "should be nil when there are no semicolons" do 200 | expect(ZoneFileFieldValidator.txt_data_semicolons?("foobar")).to be_nil 201 | end 202 | 203 | it "should be nil when there is an escaped semicolon" do 204 | expect(ZoneFileFieldValidator.txt_data_semicolons?('foo\;bar')).to be_nil 205 | end 206 | 207 | it "should be false when there is a non-escaped semicolon" do 208 | expect(ZoneFileFieldValidator.txt_data_semicolons?("foo;bar")).to be false 209 | end 210 | 211 | it "should be false when there is both an escaped and non-escaped semicolon" do 212 | expect(ZoneFileFieldValidator.txt_data_semicolons?('foo\;bar;bar')).to be false 213 | end 214 | end 215 | 216 | describe "ttl?" do 217 | min = ZoneFileFieldValidator::MIN_TTL 218 | max = ZoneFileFieldValidator::MAX_TTL 219 | 220 | it "should return true for integers between MIN_TTL and MAX_TTL" do 221 | average_ttl = (min + max) / 2 222 | 223 | expect(ZoneFileFieldValidator.ttl?("3600")).to be true 224 | expect(ZoneFileFieldValidator.ttl?(average_ttl.to_s)).to be true 225 | expect(ZoneFileFieldValidator.ttl?(min.to_s)).to be true 226 | expect(ZoneFileFieldValidator.ttl?(max.to_s)).to be true 227 | end 228 | 229 | it "should be false for TTLs out of range or that aren't number strings" do 230 | expect(ZoneFileFieldValidator.ttl?((min - 1).to_s)).to be false 231 | expect(ZoneFileFieldValidator.ttl?((max + 1).to_s)).to be false 232 | expect(ZoneFileFieldValidator.ttl?("abcd")).to be false 233 | end 234 | end 235 | 236 | describe "get_record_errors" do 237 | it "should return an empty array for valid records" do 238 | records = [ 239 | { 240 | "ttl" => "3600", 241 | "record_type" => "A", 242 | "subdomain" => "subdomain", 243 | "data" => "127.0.0.1", 244 | }, 245 | { 246 | "ttl" => "3600", 247 | "record_type" => "AAAA", 248 | "subdomain" => "subdomain", 249 | "data" => "::1", 250 | }, 251 | { 252 | "ttl" => "3600", 253 | "record_type" => "NS", 254 | "subdomain" => "@", 255 | "data" => "ns.example.com.", 256 | }, 257 | { 258 | "ttl" => "3600", 259 | "record_type" => "MX", 260 | "subdomain" => "mail", 261 | "data" => "10 mail.example.com.", 262 | }, 263 | { 264 | "ttl" => "3600", 265 | "record_type" => "TXT", 266 | "subdomain" => "@", 267 | "data" => '"arbitrary\ data\;\ and\ such"', 268 | }, 269 | { 270 | "ttl" => "3600", 271 | "record_type" => "CNAME", 272 | "subdomain" => "api", 273 | "data" => "subdomain.com.", 274 | }, 275 | ] 276 | 277 | records.each do |rec| 278 | expect(ZoneFileFieldValidator.get_record_errors(rec)).to be_empty 279 | end 280 | end 281 | 282 | it "should raise errors for missing fields" do 283 | result = ZoneFileFieldValidator.get_record_errors({}) 284 | 285 | expect(result.length).to be 4 286 | end 287 | 288 | it "should allow TXT records to have underscores in their subdomain fields" do 289 | record = { 290 | "ttl" => "3600", 291 | "record_type" => "TXT", 292 | "subdomain" => "_extra_test", 293 | "data" => '"arbitrary\ data\;\ and\ such"', 294 | } 295 | 296 | expect(ZoneFileFieldValidator.get_record_errors(record)).to be_empty 297 | end 298 | 299 | it "should raise errors for a non-escaped semicolon in a TXT data field" do 300 | record = { 301 | "ttl" => "3600", 302 | "record_type" => "TXT", 303 | "subdomain" => "_extra_test", 304 | "data" => '"arbitrary\ data;\ and\ such"', 305 | } 306 | 307 | result = ZoneFileFieldValidator.get_record_errors(record) 308 | expect(result.length).to be 1 309 | end 310 | 311 | it "should raise errors for simple errors" do 312 | record = { 313 | "ttl" => "0", 314 | "record_type" => "NONEXISTANT RECORD TYPE", 315 | "subdomain" => "b@d_domain.com", 316 | } 317 | result = ZoneFileFieldValidator.get_record_errors(record) 318 | expect(result.length).to be 4 319 | end 320 | end 321 | 322 | describe "get_zone_errors" do 323 | it "should raise an error for missing origin" do 324 | zone = { 325 | "origin" => "example.com.", 326 | "records" => [ 327 | { 328 | "ttl" => "3600", 329 | "record_type" => "A", 330 | "subdomain" => "subdomain", 331 | "data" => "127.0.0.1", 332 | }, 333 | ], 334 | } 335 | result = ZoneFileFieldValidator.get_zone_errors(zone) 336 | 337 | expect(result).to be_empty 338 | end 339 | 340 | it "should raise errors for missing or empty fields" do 341 | expect(ZoneFileFieldValidator.get_zone_errors({}).length).to be 2 342 | 343 | zone = { 344 | "origin" => "", 345 | "records" => [], 346 | } 347 | 348 | expect(ZoneFileFieldValidator.get_zone_errors(zone).length).to be 2 349 | end 350 | 351 | it "should raise an error if the origin is not a FQDN" do 352 | zone = { 353 | "origin" => "bad_domain.com", 354 | "records" => [ 355 | { 356 | "ttl" => "3600", 357 | "record_type" => "A", 358 | "subdomain" => "subdomain", 359 | "data" => "127.0.0.1", 360 | }, 361 | ], 362 | } 363 | 364 | expect(ZoneFileFieldValidator.get_zone_errors(zone).length).to be 1 365 | end 366 | end 367 | end 368 | --------------------------------------------------------------------------------