tag:blogger.com,1999:blog-80998285703099339932024-03-15T20:09:20.811-05:00Damon BreedenAn automatorDamonhttp://www.blogger.com/profile/10571355989481357458noreply@blogger.comBlogger4125tag:blogger.com,1999:blog-8099828570309933993.post-82267734771389094172022-09-03T09:53:00.001-05:002022-09-03T11:37:46.763-05:00Puppet: managing zero-knowledge secrets<p> Or: how to use /bin/true as a hinge of sorts</p><p>I was recently tasked with adding a zero-knowledge secrets module for our Puppet environment, using age and mustache. At first glance this seemed like a simple task, just decrypt the secrets and use mustache to drop them into the target files<br />This works great for single-secret files, but multi-secret files turned out to be a bit of a problem:<br /><br />$ cat demo.pp<br /># core::age<br />#<br /># define: core::age<br /># this class takes no parameters, it only installs basic requirements to use core::age::decrypt<br />#<br /># if your {secrets,templates} are in a subdir of /home/user/.age/{secrets,templates} then you must define that subdir on your own<br />#<br /># README in files/age/README.md<br /><br />class core::age::demo {<br /> $tmpdir = '/tmp/age'<br /> ['age', 'ruby-mustache',].each | $package | {<br /> case $::kernel {<br /> 'Darwin': {<br /> $pkg = $package ? {<br /> 'ruby-mustache' => 'mustache',<br /> default => $package,<br /> }<br /> $provider = $package ? {<br /> 'ruby-mustache' => 'gem',<br /> default => 'brew',<br /> }<br /> $ensure = 'present'<br /> }<br /> default: {<br /> $pkg = $package<br /> $provider = 'apt'<br /> $ensure = $package ? {<br /> # require age >=1.0.0 https://github.com/sthagen/FiloSottile-age/pull/22/files<br /> 'age' => '>=1.0.0-1~bpo11+1',<br /> default => 'present',<br /> }<br /> }<br /> }<br /> package {$pkg:<br /> ensure => $ensure,<br /> provider => $provider,<br /> }<br /> }<br /> file {<br /> default:<br /> ensure => directory,<br /> owner => 'user',<br /> group => 'user',<br /> mode => '0700',<br /> # don't allow unknown files in these dirs, they're old secrets<br /> recurse => true,<br /> purge => true,<br /> force => true,<br /> ;<br /> "/home/user/.age":<br /> recurse => remote,<br /> source => 'puppet:///modules/core/age',<br /> ;<br /> "/home/user/.age/config":<br /> # this is where the host keys live, puppet doesn't manage them<br /> purge => false,<br /> recurse => false,<br /> ;<br /> $tmpdir:<br /> owner => root,<br /> group => roo,<br /> }<br />}<br />$ cat decryptdemo.pp <br />define core::age::decryptdemo (<br /> Hash $secrets,<br /> String $template,<br /> Enum['present', 'absent'] $ensure = present,<br /> String $target = $name,<br /> Enum[<br /> 'cluster',<br /> 'host'<br /> ] $key_type = 'cluster',<br /> String $template_source = "puppet:///modules/core/age/templates/${template}",<br /> Boolean $plant_template = true,<br /> String $mode = '0600',<br /> Optional[String] $owner = undef,<br /> Optional[String] $group = undef,<br />) {<br /><br /> include core::age::demo<br /><br /> $random = generate('/bin/bash', '-c', 'tr -dc A-Za-z0-9 '/usr/bin/true',<br /> default => '/bin/true',<br /> }<br /> $false = $::kernel ? {<br /> 'Darwin' => '/usr/bin/false',<br /> default => '/bin/false',<br /> }<br /><br /> # fail if can't find the key, run max 1x per key type<br /> if ! defined(Exec["ensure ${key_type} key"]) {<br /> exec {"ensure ${key_type} key":<br /> command => $false,<br /> unless => "test -f ${keypath}",<br /> refreshonly => true,<br /> }<br /> }<br /><br /> if $plant_template {<br /> if ! defined (File[$template_path]) {<br /> file {$template_path:<br /> ensure => core::bool2ensure($plant_template),<br /> owner => 'user',<br /> group => 'user',<br /> mode => '0600',<br /> source => $template_source,<br /> }<br /> }<br /> }<br /><br /> if $ensure == 'present' {<br /> $secrets.each | $reference, $secret | {<br /> $secret_path = "/home/user/.age/secrets/${secret}.age"<br /> file {$secret_path:<br /> owner => 'user',<br /> group => 'user',<br /> mode => '0600',<br /> source => "puppet:///modules/core/age/secrets/${secret}.age",<br /> notify => Exec["create_${target}",],<br /> }<br /> ~> exec {"fail_if_cannot_decrypt_${secret}":<br /> # errors get hidden in decrypt_${secret} by the $(command substitution)<br /> command => "age --decrypt -i ${keypath} ${secret_path} > /dev/null",<br /> path => "/usr/local/bin:/bin:/usr/bin",<br /> subscribe => File[$secret_path,],<br /> logoutput => false,<br /> refreshonly => true,<br /> }<br /> ~> exec{"decrypt_${secret}":<br /> command => "echo ${reference}: \"$(age --decrypt -i ${keypath} ${secret_path})\" >> ${tmpfile}",<br /> path => "/usr/local/bin:/bin:/usr/bin",<br /> subscribe => Exec["fail_if_cannot_decrypt_${secret}",],<br /> notify => Exec["create_${target}",],<br /> require => [File[$secret_path,],],<br /> refreshonly => true,<br /> }<br /> }<br /> exec {"create_${target}":<br /> command => "mustache ${tmpfile} ${template_path} >| ${target}",<br /> logoutput => false,<br /> path => "/usr/local/bin:/bin:/usr/bin",<br /> # require $target bc otherwise puppet thinks the file doesn't exist<br /> # (bc it doesn't at the start of the first puppet run) and overwrites it blank<br /> require => [File[$template_path, $target,],],<br /> refreshonly => true,<br /> }<br /> }<br /> file {$target:<br /> # file resource is only here to apply ownership & perms<br /> ensure => $ensure,<br /> owner => $owner,<br /> group => $group,<br /> mode => $mode,<br /> }<br />}<br />$ cat ../../files/age/templates/demo_test <br />this is a test file<br />the secret is: {{{ demo_test }}}<br />the secret is above<br /><br />$ cat ../../../../manifests/site.pp<br />node /^demo[0-9].demo.net$/<br />{<br /> core::age::decryptdemo {'/home/user/demo_test':<br /> ensure => present,<br /> secrets => {<br /> 'demo_test' => 'demo_test',<br /> },<br /> template => 'demo_test',<br /> }<br />}<br /><br />$ echo test | age -a -R secrets/cluster/demo.pub -o secrets/puppet/demo_test.age <br /><br />user@demo1:~$ sudo puppet agent -t --environment demo<br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/File[/home/user/.age/secrets/demo_test.age]/ensure: defined content as '{sha256}579c545e1cbea83fe5dc5d69c36e2cbe9ed43ac12b3886202b16386e4441ac44'<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/File[/home/user/.age/secrets/demo_test.age]: Scheduling refresh of Exec[create_/home/user/demo_test]<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/File[/home/user/.age/secrets/demo_test.age]: Scheduling refresh of Exec[fail_if_cannot_decrypt_demo_test]<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/File[/home/user/.age/secrets/demo_test.age]: Scheduling refresh of Exec[fail_if_cannot_decrypt_demo_test]<br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/Exec[fail_if_cannot_decrypt_demo_test]: Triggered 'refresh' from 2 events<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/Exec[fail_if_cannot_decrypt_demo_test]: Scheduling refresh of Exec[decrypt_demo_test]<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/Exec[fail_if_cannot_decrypt_demo_test]: Scheduling refresh of Exec[decrypt_demo_test]<br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/Exec[decrypt_demo_test]: Triggered 'refresh' from 2 events<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/Exec[decrypt_demo_test]: Scheduling refresh of Exec[create_/home/user/demo_test]<br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/Exec[create_/home/user/demo_test]: Triggered 'refresh' from 2 events<br />Notice: Applied catalog in 9.65 seconds<br />user@demo1:~$ cat /home/user/demo_test<br />this is a test file<br />the secret is: test<br />the secret is above <br /><br />This looks great! But things get tricky if you add a second secret, or change one secret but not the other:<br /><br />$ echo test2 | age -a -R secrets/cluster/demo.pub -o secrets/puppet/demo_test2.age<br /><br />$ cat ../../../../manifests/site.pp<br />node /^demo[0-9].demo.net$/<br />{<br /> core::age::decryptdemo {'/home/user/demo_test':<br /> ensure => present,<br /> secrets => {<br /> 'demo_test' => 'demo_test',<br /> 'demo_test2' => 'demo_test2',<br /> },<br /> template => 'demo_test',<br /> }<br />}<br /><br />user@demo1:~$ sudo puppet agent -t --environment demo<br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/File[/home/user/.age/templates/demo_test]/content: <br />--- /home/user/.age/templates/demo_test 2022-09-02 20:14:43.003075691 +0000<br />+++ /tmp/puppet-file20220902-1060326-16onh8q 2022-09-02 20:23:47.190767817 +0000<br />@@ -1,3 +1,6 @@<br /> this is a test file<br /> the secret is: {{{ demo_test }}}<br /> the secret is above<br />+secret 2 is below<br />+secret2 is: {{{ demo_test2 }}}<br />+secret 2 is above<br /><br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/File[/home/user/.age/templates/demo_test]/content: content changed '{sha256}f1247e0b81a9a0f7899a7aa342001033728142fa89af54cc81fe3fab8e784ff8' to '{sha256}54dea21440b9c83833fcf35c2ffcadf2e7134ef2a62a9eecba56f12658ba0168'<br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/File[/home/user/.age/secrets/demo_test2.age]/ensure: defined content as '{sha256}4a930d67df8ee0af7e5a7c7574427e1b8a363fe20f534a3f0eb79a659bf07d7e'<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/File[/home/user/.age/secrets/demo_test2.age]: Scheduling refresh of Exec[create_/home/user/demo_test]<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/File[/home/user/.age/secrets/demo_test2.age]: Scheduling refresh of Exec[fail_if_cannot_decrypt_demo_test2]<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/File[/home/user/.age/secrets/demo_test2.age]: Scheduling refresh of Exec[fail_if_cannot_decrypt_demo_test2]<br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/Exec[fail_if_cannot_decrypt_demo_test2]: Triggered 'refresh' from 2 events<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/Exec[fail_if_cannot_decrypt_demo_test2]: Scheduling refresh of Exec[decrypt_demo_test2]<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/Exec[fail_if_cannot_decrypt_demo_test2]: Scheduling refresh of Exec[decrypt_demo_test2]<br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/Exec[decrypt_demo_test2]: Triggered 'refresh' from 2 events<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/Exec[decrypt_demo_test2]: Scheduling refresh of Exec[create_/home/user/demo_test]<br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decryptdemo[/home/user/demo_test]/Exec[create_/home/user/demo_test]: Triggered 'refresh' from 2 events<br />Notice: Applied catalog in 9.90 seconds<br />user@demo1:~$ sudo cat /home/user/demo_test<br />this is a test file<br />the secret is: <br />the secret is above<br />secret 2 is below<br />secret2 is: test2<br />secret 2 is above<br />user@demo1:~$<br /></p><p>As you can see, we've placed secret2 but we've lost the original secret</p><p><br />In order to work around this, I used /bin/true as a 'hinge' of sorts between the File resources and the Exec resources:<br /><br />$ cat base.pp decrypt.pp secret.pp <br /># core::age<br />#<br /># define: core::age<br /># this class takes no parameters, it only installs basic requirements to use core::age::decrypt<br />#<br /># if your {secrets,templates} are in a subdir of /home/user/.age/{secrets,templates} then you must define that subdir on your own<br />#<br /># README in files/age/README.md<br /><br />class core::age::base {<br /> $tmpdir = '/tmp/age'<br /> ['age', 'ruby-mustache',].each | $package | {<br /> case $::kernel {<br /> 'Darwin': {<br /> $pkg = $package ? {<br /> 'ruby-mustache' => 'mustache',<br /> default => $package,<br /> }<br /> $provider = $package ? {<br /> 'ruby-mustache' => 'gem',<br /> default => 'brew',<br /> }<br /> $ensure = 'present'<br /> }<br /> default: {<br /> $pkg = $package<br /> $provider = 'apt'<br /> $ensure = $package ? {<br /> # require age >=1.0.0 https://github.com/sthagen/FiloSottile-age/pull/22/files<br /> 'age' => '>=1.0.0-1~bpo11+1',<br /> default => 'present',<br /> }<br /> }<br /> }<br /> package {$pkg:<br /> ensure => $ensure,<br /> provider => $provider,<br /> }<br /> }<br /> file {<br /> default:<br /> ensure => directory,<br /> owner => 'user',<br /> group => 'user',<br /> mode => '0700',<br /> # don't allow unknown files in these dirs, they're old secrets<br /> recurse => true,<br /> purge => true,<br /> force => true,<br /> ;<br /> "/home/user/.age":<br /> recurse => remote,<br /> source => 'puppet:///modules/core/age',<br /> ;<br /> "/home/user/.age/config":<br /> # this is where the host keys live, puppet doesn't manage them<br /> purge => false,<br /> recurse => false,<br /> ;<br /> $tmpdir:<br /> owner => root,<br /> group => root,<br /> }<br />}<br /># define: core::age::decrypt<br />#<br /># README in files/age/README.md<br />#<br /># Parameters:<br /># [*ensure*] - Ensure that the files is (present|absent). Default: present<br /># [*secrets*] - A hash of {'references' => 'secrets'}. Required<br /># [*template*] - A file template that will be placed on the target host, for age+mustache to fill. Required<br /># - This is not the final file, but a mustache template that age+mustache will interact with to build the final target<br /># [*target*] - The target file to be created. Default: $name.<br /># [*key_type*] - The key type, choose between {cluster,host}. Default: cluster<br /># [*template_source*] - The template source file. Default: puppet:///modules/core/age/templates/${template}. Required.<br /># [*plant_template*] - If you want puppet to automatically source the template file using $template_source. Default: true<br /># - Set to false in order to fill a template file with other (non-secret) vars using a file resource + epp<br /># [*mode*] - The file mode to be set on the target. Default: '0600'. Optional<br /># [*user*] - The file owner to be set on the target. Default: undef. Optional<br /># [*group*] - The file group to be set on the target. Default: undef. Optional<br />#<br /># A note about the use of ` > /dev/null` in many of the `exec` statements in this file<br /># The purpose of this module is to _not_ leak secrets.<br /># By default an exec statment will push its results back to the puppet catalog, even when `logoutput => false,`<br /># Hence we use `> /dev/null` to keep the secrets from being placed on the puppet catalog<br />#<br /># Because this is a zero-knowledge module, puppet can't know if the content of your target file has changed outside of this module<br /># The only thing that will update the file is<br /># - removing the target file<br /># - removing the corresponding secret or template in ~/.age/{secrets,templates}<br /># - changing the secret in puppet<br /># simply changing the content of the target file will not trigger an update of the secrets<br />#<br /># or you can wait one hour and an automatic refresh of the secrets will be performed<br /><br />define core::age::decrypt (<br /> Hash $secrets,<br /> String $template,<br /> Enum['present', 'absent'] $ensure = present,<br /> String $target = $name,<br /> Enum[<br /> 'cluster',<br /> 'host'<br /> ] $key_type = 'cluster',<br /> String $template_source = "puppet:///modules/core/age/templates/${template}",<br /> Boolean $plant_template = true,<br /> String $mode = '0600',<br /> Optional[String] $owner = undef,<br /> Optional[String] $group = undef,<br />) {<br /><br /> include core::age::base<br /><br /> $random = generate('/bin/bash', '-c', 'tr -dc A-Za-z0-9 '/usr/bin/true',<br /> default => '/bin/true',<br /> }<br /> $false = $::kernel ? {<br /> 'Darwin' => '/usr/bin/false',<br /> default => '/bin/false',<br /> }<br /><br /> exec {"kickoff_${target}":<br /> # a harmless kicker-offer that every file notifies and every exec subscribes to<br /> # this is the entrypoint to any secret<br /> command => $true,<br /> refreshonly => true,<br /> }<br /><br /> # fail if can't find the key, run max 1x per key type<br /> if ! defined(Exec["ensure ${key_type} key"]) {<br /> exec {"ensure ${key_type} key":<br /> command => $false,<br /> unless => "test -f ${keypath}",<br /> refreshonly => true,<br /> subscribe => Exec["kickoff_${target}",],<br /> }<br /> }<br /><br /> if $plant_template {<br /> if ! defined (File[$template_path]) {<br /> file {$template_path:<br /> ensure => core::bool2ensure($plant_template),<br /> owner => 'user',<br /> group => 'user',<br /> mode => '0600',<br /> source => $template_source,<br /> notify => Exec["kickoff_${target}",],<br /> }<br /> }<br /> }<br /><br /> if $ensure == 'present' {<br /> $secrets.each | $reference, $secret | {<br /> core::age::secret {$secret:<br /> reference => $reference,<br /> tmpfile => $tmpfile,<br /> target => $target,<br /> keypath => $keypath,<br /> tag => $::hostname,<br /> }<br /> }<br /> # collect all the secrets before working on decryption<br /> Core::Age::Secret <<| tag == $::hostname |>><br /> ~> exec {"create_${target}":<br /> command => "mustache ${tmpfile} ${template_path} >| ${target}",<br /> logoutput => false,<br /> path => "/usr/local/bin:/bin:/usr/bin",<br /> # require $target bc otherwise puppet thinks the file doesn't exist<br /> # (bc it doesn't at the start of the first puppet run) and overwrites it blank<br /> require => [File[$template_path, $target,],],<br /> subscribe => Exec["kickoff_${target}",],<br /> refreshonly => true,<br /> }<br /> exec {"hourly_refresh_${target}":<br /> # allows ${target} to be refreshed by other changes (as often as necessary) or regularly on a schedule<br /> command => $true,<br /> notify => Exec["kickoff_${target}",],<br /> schedule => hourly,<br /> }<br /> }<br /> file {$target:<br /> # file resource is only here to apply ownership &amp; perms<br /> ensure => $ensure,<br /> owner => $owner,<br /> group => $group,<br /> mode => $mode,<br /> notify => Exec["kickoff_${target}",],<br /> }<br />}<br /># core::age::secret<br /><br />define core::age::secret (<br /> $reference,<br /> $tmpfile,<br /> $target,<br /> $keypath,<br /> $secret = $name,<br />) {<br /><br /> $secret_path = "/home/user/.age/secrets/${secret}.age"<br /> file {$secret_path:<br /> owner => 'user',<br /> group => 'user',<br /> mode => '0600',<br /> source => "puppet:///modules/core/age/secrets/${secret}.age",<br /> notify => Exec["kickoff_${target}",],<br /> }<br /> ~> exec {"fail_if_cannot_decrypt_${secret}":<br /> # errors get hidden in decrypt_${secret} by the $(command substitution)<br /> command => "age --decrypt -i ${keypath} ${secret_path} > /dev/null",<br /> path => ":/usr/local/bin:/bin:/usr/bin",<br /> subscribe => Exec["kickoff_${target}",],<br /> logoutput => false,<br /> refreshonly => true,<br /> }<br /> ~> exec{"decrypt_${secret}":<br /> command => "echo ${reference}: \"$(age --decrypt -i ${keypath} ${secret_path})\" >> ${tmpfile}",<br /> path => "/usr/local/bin:/bin:/usr/bin",<br /> subscribe => Exec["kickoff_${target}",],<br /> notify => Exec["create_${target}",],<br /> require => [File[$secret_path,],],<br /> refreshonly => true,<br /> }<br />}<br />node /^demo[0-9].demo.net$/<br />{<br /> core::age::decrypt {'/home/user/demo_test':<br /> ensure => present,<br /> secrets => {<br /> 'demo_test' => 'demo_test',<br /> },<br /> template => 'demo_test',<br /> }<br />}<br />user@demo4:~$ sudo puppet agent -t --environment damon<br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/File[/home/user/demo_test]/ensure: created (corrective)<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/File[/home/user/demo_test]: Scheduling refresh of Exec[kickoff_/home/user/demo_test]<br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Exec[kickoff_/home/user/demo_test]: Triggered 'refresh' from 1 event<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Exec[kickoff_/home/user/demo_test]: Scheduling refresh of Exec[create_/home/user/demo_test]<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Exec[kickoff_/home/user/demo_test]: Scheduling refresh of Exec[fail_if_cannot_decrypt_demo_test]<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Exec[kickoff_/home/user/demo_test]: Scheduling refresh of Exec[decrypt_demo_test]<br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Core::Age::Secret[demo_test]/Exec[fail_if_cannot_decrypt_demo_test]: Triggered 'refresh' from 1 event<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Core::Age::Secret[demo_test]/Exec[fail_if_cannot_decrypt_demo_test]: Scheduling refresh of Exec[decrypt_demo_test]<br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Core::Age::Secret[demo_test]/Exec[decrypt_demo_test]: Triggered 'refresh' from 2 events<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Core::Age::Secret[demo_test]/Exec[decrypt_demo_test]: Scheduling refresh of Exec[create_/home/user/demo_test]<br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Exec[create_/home/user/demo_test]: Triggered 'refresh' from 2 events<br />Notice: Applied catalog in 10.44 seconds<br />user@demo4:~$ sudo cat /home/user/demo_test<br />this is a test file<br />the secret is: test<br />the secret is above<br />secret 2 is below<br />secret2 is: <br />secret 2 is above<br /><br />node /^demo[0-9].demo.net$/<br />{<br /> core::age::decrypt {'/home/user/demo_test':<br /> ensure => present,<br /> secrets => {<br /> 'demo_test' => 'demo_test',<br /> 'demo_test2' => 'demo_test2',<br /> },<br /> template => 'demo_test',<br /> }<br />}<br /><br />user@demo4:~$ sudo puppet agent -t --environment damon<br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Core::Age::Secret[demo_test2]/File[/home/user/.age/secrets/demo_test2.age]/mode: mode changed '0700' to '0600'<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Core::Age::Secret[demo_test2]/File[/home/user/.age/secrets/demo_test2.age]: Scheduling refresh of Exec[kickoff_/home/user/demo_test]<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Core::Age::Secret[demo_test2]/File[/home/user/.age/secrets/demo_test2.age]: Scheduling refresh of Exec[fail_if_cannot_decrypt_demo_test2]<br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Exec[kickoff_/home/user/demo_test]: Triggered 'refresh' from 1 event<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Exec[kickoff_/home/user/demo_test]: Scheduling refresh of Exec[create_/home/user/demo_test]<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Exec[kickoff_/home/user/demo_test]: Scheduling refresh of Exec[fail_if_cannot_decrypt_demo_test]<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Exec[kickoff_/home/user/demo_test]: Scheduling refresh of Exec[decrypt_demo_test]<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Exec[kickoff_/home/user/demo_test]: Scheduling refresh of Exec[fail_if_cannot_decrypt_demo_test2]<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Exec[kickoff_/home/user/demo_test]: Scheduling refresh of Exec[decrypt_demo_test2]<br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Core::Age::Secret[demo_test]/Exec[fail_if_cannot_decrypt_demo_test]: Triggered 'refresh' from 1 event<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Core::Age::Secret[demo_test]/Exec[fail_if_cannot_decrypt_demo_test]: Scheduling refresh of Exec[decrypt_demo_test]<br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Core::Age::Secret[demo_test]/Exec[decrypt_demo_test]: Triggered 'refresh' from 2 events<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Core::Age::Secret[demo_test]/Exec[decrypt_demo_test]: Scheduling refresh of Exec[create_/home/user/demo_test]<br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Core::Age::Secret[demo_test2]/Exec[fail_if_cannot_decrypt_demo_test2]: Triggered 'refresh' from 2 events<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Core::Age::Secret[demo_test2]/Exec[fail_if_cannot_decrypt_demo_test2]: Scheduling refresh of Exec[decrypt_demo_test2]<br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Core::Age::Secret[demo_test2]/Exec[decrypt_demo_test2]: Triggered 'refresh' from 2 events<br />Info: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Core::Age::Secret[demo_test2]/Exec[decrypt_demo_test2]: Scheduling refresh of Exec[create_/home/user/demo_test]<br />Notice: /Stage[main]/Main/Node[__node_regexp__demo0-9.demo.net]/Core::Age::Decrypt[/home/user/demo_test]/Exec[create_/home/user/demo_test]: Triggered 'refresh' from 3 events<br />Notice: Applied catalog in 10.53 seconds<br />user@demo4:~$ sudo cat /home/user/demo_test<br />this is a test file<br />the secret is: test<br />the secret is above<br />secret 2 is below<br />secret2 is: test2<br />secret 2 is above<br />user@demo4:~$ <br /><br />As you can see, we have successfully added a secret to our manifest but convinced Puppet to decrypt all secrets, and only then run mustache to drop the secrets in the file<br />This is acheived by having every File resource notify our 'kicker-offer' and every Exec resource subscribe to the 'kicker-offer', and by moving secrets to its own defined type that all get collected before the decrypt step runs<br /></p><p></p>Damonhttp://www.blogger.com/profile/10571355989481357458noreply@blogger.com0tag:blogger.com,1999:blog-8099828570309933993.post-51035009204666125952022-05-13T11:49:00.001-05:002022-05-17T14:43:23.775-05:00Puppet: using Facts in an Exec clause (onlyif or unless)<p> According to <a href="https://www.blogger.com/u/1/#" rel="nofollow">this issue</a> on Puppet labs, there's simply no way to use facts in an Puppet Exec clause for the purpose of onlyif or unless, as such:</p>
<pre><code>exec {'some_exec':
command => '/usr/bin/cmd',
# this `onlyif` will cause your manifest to fail
onlyif => $::facts[some_fact] == true,
} </code></pre>
<p>However I found a simple workaround for this, simply set a var with your facts to either 'true' or 'false' (string values) and use those in your `onlyif` block</p>
<pre><code>if $::facts[some_fact] == true {
$exec_true = 'true'
}
else {$exec_true = 'false'}</code></pre>
<p>then use that in your exec block:</p>
<pre><code>exec {'some_exec':
command => '/usr/bin/cmd',
# literally calls '/usr/bin/{true,false}'
onlyif => "/usr/bin/${exec_true},
} </code></pre>
<p></p>Damonhttp://www.blogger.com/profile/10571355989481357458noreply@blogger.com0tag:blogger.com,1999:blog-8099828570309933993.post-88744191746382203712019-02-09T23:06:00.000-05:002019-02-09T23:28:51.900-05:00Deploying ESXi 6.0u3 via PXE in a labIn my never ending quest for automation, I thought I'd experiment with what is required to deploy ESXi over the network to new servers.<br />
<br />
In order to script this, and know that I hit every checkmark, I decided to write it first using Vagrant.<br />
<br />
I wrote this because other posts on the subject are close, but not exactly spot-on. This post, using Vagrant, is guaranteed because it's built from scratch every time I deploy it. I destroyed and rebuilt the entire environment multiple times while creating this post.<br />
<br />
It uses these packages:<br />
<br />
<style type="text/css">
p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #2fb41d}
span.s1 {font-variant-ligatures: no-common-ligatures}
</style>
<br />
<div class="p1">
<span class="s1">dhcp <span class="Apple-converted-space"> </span>x86_64<span class="Apple-converted-space"> </span>12:4.1.1-63.P1.el6.centos</span></div>
<div class="p1">
<span class="s1">
<style type="text/css">
p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #2fb41d}
span.s1 {font-variant-ligatures: no-common-ligatures}
</style>
</span></div>
<div class="p1">
<span class="s1">syslinux <span class="Apple-converted-space"> </span>x86_64<span class="Apple-converted-space"> </span>4.04-3.el6</span></div>
<div class="p1">
<span class="s1">
<style type="text/css">
p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo; color: #2fb41d}
span.s1 {font-variant-ligatures: no-common-ligatures}
</style>
</span></div>
<div class="p1">
<span class="s1">tftp-server<span class="Apple-converted-space"> </span>x86_64<span class="Apple-converted-space"> </span>0.49-8.el6</span></div>
<br />
If those packages change, this tutorial might become out of date. You should be able to force a version of each package to install in the bootstrap.sh file.<br />
<br />
This tutorial uses CentOS 6.4 64-bit as a DHCP server and Vagrant.<br />
<br />
All the code needed for this tutorial is available at <a href="https://github.com/volvo64/VMwarePXE" target="_blank">https://github.com/volvo64/VMwarePXE</a><br />
<br />
You'll also need an ESXi ISO. I used VMware-VMvisor-Installer-6.0.0.update03-5050593.x86_64.iso. This should be in the same directory as your Vagrantfile and bootstrap.sh. Make sure to change references to this ISO in bootstrap.sh.<br />
<br />
Sorry for the weird formatting, Blogger isn't the best platform to write about code on. Review the code on Github for that.<br />
<br />
First, in Vagrantfile, we're going to specify Centos64:<br />
<blockquote class="tr_bq">
<div style="background-color: #1e1e1e; color: #d4d4d4; font-family: Menlo, Monaco, "Courier New", monospace; font-size: 12px; line-height: 18px; white-space: pre;">
<div>
config.vm.box = <span style="color: #ce9178;">"forumone/centos64-64"</span></div>
</div>
</blockquote>
We also need to specify a bootstrap file:<br />
<blockquote class="tr_bq">
<div style="background-color: #1e1e1e; color: #d4d4d4; font-family: Menlo, Monaco, "Courier New", monospace; font-size: 12px; line-height: 18px; white-space: pre;">
config.vm.provision :shell, path: <span style="color: #ce9178;">"bootstrap.sh"</span></div>
</blockquote>
Lastly, in the Vagrantfile we need to specify a private network to perform DHCP on:<br />
<blockquote class="tr_bq">
<div style="background-color: #1e1e1e; color: #d4d4d4; font-family: Menlo, Monaco, "Courier New", monospace; font-size: 12px; line-height: 18px; white-space: pre;">
config.vm.network <span style="color: #ce9178;">"private_network"</span>, ip: <span style="color: #ce9178;">"192.168.33.10"</span></div>
</blockquote>
192.168.33.0/24 works for me, you should use what works for you.<br />
<br />
That's all for the Vagrantfile.<br />
<br />
bootstrap.sh actually builds the services once the Vagrant box has been created. We need a few programs installed. Let's start with dhcp, tftp-server and syslinux:<br />
<blockquote class="tr_bq">
<div style="background-color: #1e1e1e; color: #d4d4d4; font-family: Menlo, Monaco, "Courier New", monospace; font-size: 12px; line-height: 18px; white-space: pre;">
<div>
<span style="color: #6a9955;">#!/usr/bin/env bash</span></div>
<br />
<div>
yum -y install dhcp tftp-server syslinux</div>
</div>
</blockquote>
Next we need the contents of the ESXi ISO available in our tftpboot directory. We can mount it from the /vagrant/ directory that's automatically shared from your host:<br />
<blockquote class="tr_bq">
<span style="background-color: #1e1e1e; color: #d4d4d4; font-family: "menlo" , "monaco" , "courier new" , monospace; font-size: 12px; white-space: pre;">mount -o loop /vagrant/VMware-VMvisor-Installer-6.0.0.update03-5050593.x86_64.iso /mnt/</span></blockquote>
Copy the files:<br />
<blockquote class="tr_bq">
<span style="background-color: #1e1e1e; color: #d4d4d4; font-family: "menlo" , "monaco" , "courier new" , monospace; font-size: 12px; white-space: pre;">cp -rf /mnt /var/lib/tftpboot/esxi60u3</span></blockquote>
And finally unmount the ISO:<br />
<blockquote class="tr_bq">
<span style="background-color: #1e1e1e; color: #d4d4d4; font-family: "menlo" , "monaco" , "courier new" , monospace; font-size: 12px; white-space: pre;">umount /mnt/</span></blockquote>
Now that we have DHCP installed and the ISO copied we need to create the DHCP options. This block writes the options to /etc/dhcp/dhcpd.conf:<br />
<blockquote class="tr_bq">
<div style="background-color: #1e1e1e; color: #d4d4d4; font-family: Menlo, Monaco, "Courier New", monospace; font-size: 12px; line-height: 18px; white-space: pre;">
<div>
<span style="color: #dcdcaa;">echo</span> <span style="color: #ce9178;">'# dhcpd.conf</span></div>
<div>
<span style="color: #ce9178;">#</span></div>
<div>
<span style="color: #ce9178;"># Sample configuration file for ISC dhcpd</span></div>
<div>
<span style="color: #ce9178;">#</span></div>
<br />
<div>
<span style="color: #ce9178;"># option definitions common to all supported networks...</span></div>
<div>
<span style="color: #ce9178;">option domain-name "damon.local";</span></div>
<div>
<span style="color: #ce9178;">option domain-name-servers localhost.localhost;</span></div>
<br />
<div>
<span style="color: #ce9178;">default-lease-time 600;</span></div>
<div>
<span style="color: #ce9178;">max-lease-time 7200;</span></div>
<br />
<div>
<span style="color: #ce9178;"># If this DHCP server is the official DHCP server for the local</span></div>
<div>
<span style="color: #ce9178;"># network, the authoritative directive should be uncommented.</span></div>
<div>
<span style="color: #ce9178;">authoritative;</span></div>
<br />
<div>
<span style="color: #ce9178;"># Use this to send dhcp log messages to a different log file (you also</span></div>
<div>
<span style="color: #ce9178;"># have to hack syslog.conf to complete the redirection).</span></div>
<div>
<span style="color: #ce9178;">log-facility local7;</span></div>
<br />
<div>
<span style="color: #ce9178;">allow booting;</span></div>
<div>
<span style="color: #ce9178;">allow bootp;</span></div>
<div>
<span style="color: #ce9178;">option client-system-arch code 93 = unsigned integer 16;</span></div>
<br />
<div>
<span style="color: #ce9178;"># This is a very basic subnet declaration.</span></div>
<br />
<div>
<span style="color: #ce9178;">subnet 192.168.33.0 netmask 255.255.255.0 {</span></div>
<div>
<span style="color: #ce9178;"> range 192.168.33.100 192.168.33.110;</span></div>
<div>
<span style="color: #ce9178;"> option routers 192.168.33.10;</span></div>
<div>
<span style="color: #ce9178;">}</span></div>
<br />
<div>
<span style="color: #ce9178;">class "pxeclients" {</span></div>
<div>
<span style="color: #ce9178;"> match if substring(option vendor-class-identifier, 0, 9) = "PXEClient";</span></div>
<div>
<span style="color: #ce9178;"> # specifies the TFTP Server</span></div>
<div>
<span style="color: #ce9178;"> next-server 192.168.33.10;</span></div>
<div>
<span style="color: #ce9178;"> if option client-system-arch = 00:07 or option client-system-arch = 00:09 {</span></div>
<div>
<span style="color: #ce9178;"> # PXE over EFI firmware</span></div>
<div>
<span style="color: #ce9178;"> filename = "esxi60u3/mboot.efi";</span></div>
<div>
<span style="color: #ce9178;"> } else {</span></div>
<div>
<span style="color: #ce9178;"> # PXE over BIOS firmware</span></div>
<div>
<span style="color: #ce9178;"> filename = "pxelinux.0";</span></div>
<div>
<span style="color: #ce9178;"> }</span></div>
<div>
<span style="color: #ce9178;">}</span></div>
<div>
<span style="color: #ce9178;">'</span> > /etc/dhcp/dhcpd.conf</div>
</div>
</blockquote>
We need to do the same for the TFTP options in /etc/xinetd.d/tftp:<br />
<blockquote class="tr_bq">
<div style="background-color: #1e1e1e; color: #d4d4d4; font-family: Menlo, Monaco, "Courier New", monospace; font-size: 12px; line-height: 18px; white-space: pre;">
<div>
<span style="color: #dcdcaa;">echo</span> <span style="color: #ce9178;">'service tftp</span></div>
<div>
<span style="color: #ce9178;">{</span></div>
<div>
<span style="color: #ce9178;"> socket_type = dgram</span></div>
<div>
<span style="color: #ce9178;"> protocol = udp</span></div>
<div>
<span style="color: #ce9178;"> wait = yes</span></div>
<div>
<span style="color: #ce9178;"> user = root</span></div>
<div>
<span style="color: #ce9178;"> server = /usr/sbin/in.tftpd</span></div>
<div>
<span style="color: #ce9178;"> server_args = -s /var/lib/tftpboot</span></div>
<div>
<span style="color: #ce9178;"> disable = no </span></div>
<div>
<span style="color: #ce9178;"> per_source = 11</span></div>
<div>
<span style="color: #ce9178;"> cps = 100 2</span></div>
<div>
<span style="color: #ce9178;"> flags = IPv4</span></div>
<div>
<span style="color: #ce9178;">}'</span> > /etc/xinetd.d/tftp</div>
</div>
</blockquote>
The original boot.cfg that we copied from the ESXi ISO contains references to "/" all over the place that are no longer valid. We need to remove them. To do that:<br />
<blockquote class="tr_bq">
<div style="background-color: #1e1e1e; color: #d4d4d4; font-family: Menlo, Monaco, "Courier New", monospace; font-size: 12px; line-height: 18px; white-space: pre;">
sed -i <span style="color: #ce9178;">'s/\///g'</span> /var/lib/tftpboot/esxi60u3/boot.cfg</div>
</blockquote>
We need to create some directories and move some files to make PXE boot:<br />
<blockquote class="tr_bq">
<span style="background-color: #1e1e1e; color: #d4d4d4; font-family: "menlo" , "monaco" , "courier new" , monospace; font-size: 12px; white-space: pre;">mkdir -p /var/lib/tftpboot/pxelinux.cfg</span></blockquote>
<blockquote class="tr_bq">
<span style="background-color: #1e1e1e; color: #d4d4d4; font-family: "menlo" , "monaco" , "courier new" , monospace; font-size: 12px; white-space: pre;">cp /usr/share/syslinux/pxelinux.0 /var/lib/tftpboot/</span></blockquote>
<blockquote class="tr_bq">
<span style="background-color: black;"><span style="color: #dcdcaa; font-family: "menlo" , "monaco" , "courier new" , monospace; font-size: 12px; white-space: pre;">echo</span><span style="color: #d4d4d4; font-family: "menlo" , "monaco" , "courier new" , monospace; font-size: 12px; white-space: pre;"> y </span><span style="color: #d4d4d4; font-family: "menlo" , "monaco" , "courier new" , monospace; font-size: 12px; white-space: pre;">|</span><span style="color: #d4d4d4; font-family: "menlo" , "monaco" , "courier new" , monospace; font-size: 12px; white-space: pre;"> cp /usr/share/syslinux/menu.c32 /var/lib/tftpboot/esxi60u3/</span> </span></blockquote>
The last change we need is to write the menu.c32 file:<br />
<blockquote class="tr_bq">
<div style="background-color: #1e1e1e; color: #d4d4d4; font-family: Menlo, Monaco, "Courier New", monospace; font-size: 12px; line-height: 18px; white-space: pre;">
<div>
<span style="color: #dcdcaa;">echo</span> <span style="color: #ce9178;">'DEFAULT esxi60u3/menu.c32</span></div>
<div>
<span style="color: #ce9178;">MENU TITLE ESXi-6.0 Boot Menu</span></div>
<div>
<span style="color: #ce9178;">NOHALT 1</span></div>
<div>
<span style="color: #ce9178;">PROMPT 0</span></div>
<div>
<span style="color: #ce9178;">TIMEOUT 300</span></div>
<div>
<span style="color: #ce9178;">LABEL install</span></div>
<div>
<span style="color: #ce9178;">KERNEL esxi60u3/mboot.c32</span></div>
<div>
<span style="color: #ce9178;">APPEND -c esxi60u3/boot.cfg</span></div>
<div>
<span style="color: #ce9178;">MENU LABEL ESXi-6.0U3 ^Installer</span></div>
<div>
<span style="color: #ce9178;">LABEL hddboot</span></div>
<div>
<span style="color: #ce9178;">LOCALBOOT 0x80</span></div>
<div>
<span style="color: #ce9178;">MENU LABEL ^Boot from local disk'</span> > /var/lib/tftpboot/pxelinux.cfg/default</div>
</div>
</blockquote>
And finally restart some services:<br />
<blockquote class="tr_bq">
<div style="background-color: #1e1e1e; color: #d4d4d4; font-family: Menlo, Monaco, "Courier New", monospace; font-size: 12px; line-height: 18px; white-space: pre;">
<div>
/etc/init.d/xinetd restart</div>
</div>
</blockquote>
<br />
<blockquote class="tr_bq">
<span style="background-color: #1e1e1e; color: #d4d4d4; font-family: "menlo" , "monaco" , "courier new" , monospace; font-size: 12px; white-space: pre;">/etc/init.d/dhcpd restart</span></blockquote>
<br />
<div style="background-color: #1e1e1e; color: #d4d4d4; font-family: Menlo, Monaco, "Courier New", monospace; font-size: 12px; line-height: 18px; white-space: pre;">
<blockquote class="tr_bq">
/etc/init.d/iptables stop</blockquote>
<div>
</div>
</div>
<br />
<blockquote class="tr_bq">
<span style="background-color: #1e1e1e; color: #d4d4d4; font-family: "menlo" , "monaco" , "courier new" , monospace; font-size: 12px; white-space: pre;">/etc/init.d/ip6tables stop</span></blockquote>
Run 'vagrant up' and you should have a server after a few minutes!<br />
<br />
To verify the network interfaces, check out its network config. You should see two NICs attached:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://2.bp.blogspot.com/-BM0andkYTzY/XF-fdnLzARI/AAAAAAAAAXk/xo42xK-8R_kQYRDJYxOENCpItR-GAm-2ACLcBGAs/s1600/Screen%2BShot%2B2019-02-09%2Bat%2B10.50.00%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="250" data-original-width="659" height="121" src="https://2.bp.blogspot.com/-BM0andkYTzY/XF-fdnLzARI/AAAAAAAAAXk/xo42xK-8R_kQYRDJYxOENCpItR-GAm-2ACLcBGAs/s320/Screen%2BShot%2B2019-02-09%2Bat%2B10.50.00%2BPM.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://3.bp.blogspot.com/--w1XVLIqSqY/XF-fjdfyK3I/AAAAAAAAAXo/U1CVsikkQNovumDnAS9NGsGhWACr5hdLQCLcBGAs/s1600/Screen%2BShot%2B2019-02-09%2Bat%2B10.50.26%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="250" data-original-width="659" height="121" src="https://3.bp.blogspot.com/--w1XVLIqSqY/XF-fjdfyK3I/AAAAAAAAAXo/U1CVsikkQNovumDnAS9NGsGhWACr5hdLQCLcBGAs/s320/Screen%2BShot%2B2019-02-09%2Bat%2B10.50.26%2BPM.png" width="320" /></a></div>
Adapter 1 is your management interface, Adapter 2 is your internal interface.<br />
<br />
Create a new VM in Virtualbox. Give it at least 4GB of RAM and a little storage. Make sure the network is attached to the same network as Adapter 2:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-jqak-oO1EkM/XF-f7dcAcyI/AAAAAAAAAX0/-WusQmkPFdg60MmLQQkrrxaJ70QtjbA5wCLcBGAs/s1600/Screen%2BShot%2B2019-02-09%2Bat%2B10.52.00%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="250" data-original-width="659" height="121" src="https://1.bp.blogspot.com/-jqak-oO1EkM/XF-f7dcAcyI/AAAAAAAAAX0/-WusQmkPFdg60MmLQQkrrxaJ70QtjbA5wCLcBGAs/s320/Screen%2BShot%2B2019-02-09%2Bat%2B10.52.00%2BPM.png" width="320" /></a></div>
In order to boot it you need to assign 2 CPUs:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-1ooL6vfd1XI/XF-gG_n10oI/AAAAAAAAAX4/Kh68VWrrtF8c8qN2fcAiCscZeOrcY_qgACLcBGAs/s1600/Screen%2BShot%2B2019-02-09%2Bat%2B10.52.39%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="287" data-original-width="659" height="139" src="https://1.bp.blogspot.com/-1ooL6vfd1XI/XF-gG_n10oI/AAAAAAAAAX4/Kh68VWrrtF8c8qN2fcAiCscZeOrcY_qgACLcBGAs/s320/Screen%2BShot%2B2019-02-09%2Bat%2B10.52.39%2BPM.png" width="320" /></a></div>
Unfortunately since Virtualbox doesn't allow for Nested Virtualization (Nested VT-x/AMD-V) we won't actually be able to run anything on these hypervisors.<br />
<br />
Start up your test VM, use F12 and boot it from the LAN and it will show you the ESXi installer:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://4.bp.blogspot.com/-Ej2iPRxkRdY/XF-gtUk9jvI/AAAAAAAAAYE/KoQm61tX7VgnUL1AT8q5zKpMyG6geIdGQCLcBGAs/s1600/Screen%2BShot%2B2019-02-09%2Bat%2B10.54.15%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="443" data-original-width="729" height="194" src="https://4.bp.blogspot.com/-Ej2iPRxkRdY/XF-gtUk9jvI/AAAAAAAAAYE/KoQm61tX7VgnUL1AT8q5zKpMyG6geIdGQCLcBGAs/s320/Screen%2BShot%2B2019-02-09%2Bat%2B10.54.15%2BPM.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-hd801CyTOiM/XF-gtUT-GgI/AAAAAAAAAYI/XaIyvFTcGbow2lcTk26y6Yj4IAPs-W7AgCLcBGAs/s1600/Screen%2BShot%2B2019-02-09%2Bat%2B10.54.44%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="261" data-original-width="287" src="https://1.bp.blogspot.com/-hd801CyTOiM/XF-gtUT-GgI/AAAAAAAAAYI/XaIyvFTcGbow2lcTk26y6Yj4IAPs-W7AgCLcBGAs/s1600/Screen%2BShot%2B2019-02-09%2Bat%2B10.54.44%2BPM.png" /></a></div>
After this point just install ESXi as normal and it should load up fine!<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://4.bp.blogspot.com/-tpQ4VWWr45U/XF-h_iwaMkI/AAAAAAAAAYY/uOwYpvGgM2QaRiXFBYQt9oYkB_TKlIk8wCLcBGAs/s1600/Screen%2BShot%2B2019-02-09%2Bat%2B11.00.50%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="482" data-original-width="591" height="260" src="https://4.bp.blogspot.com/-tpQ4VWWr45U/XF-h_iwaMkI/AAAAAAAAAYY/uOwYpvGgM2QaRiXFBYQt9oYkB_TKlIk8wCLcBGAs/s320/Screen%2BShot%2B2019-02-09%2Bat%2B11.00.50%2BPM.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Thanks to:</div>
<div class="separator" style="clear: both; text-align: left;">
<a href="https://www.virtuallyghetto.com/2015/10/support-for-uefi-pxe-boot-introduced-in-esxi-6-0.html" target="_blank">https://www.virtuallyghetto.com/2015/10/support-for-uefi-pxe-boot-introduced-in-esxi-6-0.html</a></div>
<a href="http://www.vstellar.com/2017/07/25/automating-esxi-deployment-using-pxe-boot-and-kickstart/" target="_blank">http://www.vstellar.com/2017/07/25/automating-esxi-deployment-using-pxe-boot-and-kickstart/</a><br />
<br />
Will investigate stateless ESXi/running a real config directly from PXE soon, as well as upgrading to newer ESXi releases via PXE.Damonhttp://www.blogger.com/profile/10571355989481357458noreply@blogger.com0tag:blogger.com,1999:blog-8099828570309933993.post-91220282124217587492016-12-07T13:15:00.001-05:002016-12-07T13:15:18.014-05:00Password Security and youSome of my clients approach me regarding potentially breached accounts, so I thought it best to explain why good password policies are essential in 2016.<br />
<br />
The website <a href="https://haveibeenpwned.com/">https://haveibeenpwned.com</a> lists nearly 2 billion breached accounts from 168 websites. These are breaches that have been released publicly, meaning that they're probably only a small fraction of total breached accounts in existence. In reality, breaches that have not been made known publicly, or haven't even been discovered likely comprise at least as many as are known. With this in mind, the only safe mindset to use today is to assume that your username and password have been compromised, and to implement good password hygiene to limit the damage that can be done.<br />
<br />
A good password:<br />
<br />
<ul>
<li>is long</li>
<li>is not shared between different services</li>
<li>contains a mixture of upper and lowercase letters, numbers, and symbols</li>
<li>is not a dictionary word</li>
<li>has no personal meaning to the user (i.e. 'F1@c0' is a bad password, because it refers to my dog's name)</li>
<li>doesn't follow a recognizable pattern (i.e. Cuenca2015 is bad, because I might try Cuenca2014 or Cuenca2016 on other sites).</li>
</ul>
<br />
Lastpass helps us accomplish the goals here. Lastpass is a program akin to saved passwords in Chrome, or the piece of paper on which you record all of your usernames and passwords. However, unlike both of those, Lastpass is more secure. Lastpass helps you generate completely random passwords for each and every site- that way, if your Paypal password is leaked, that same password won't work on your bank account. Lastpass is also more securable in that you can have it log out after a certain period of time, forcing a master password re-entry before logins are made available again.<br />
<br />
I encourage you to look through <a href="https://lastpass.com/how-it-works/">https://lastpass.com/how-it-works/</a> to see how the program works. Also, they've recently made mobile use free, so I encourage you to install Lastpass on your phones as well as computers.<br />
<br />
Once you start down this road, you'll quickly find yourself fixing 20 years of bad password policy. This process is not going to be easy. You can expect to spend a year finding old accounts and changing old passwords to be more secure. It's important though to start with the high-risk accounts- financial and email (and Facebook), so that if your Linkedin account is breached, it won't affect your bank accounts.<br />
<br />
I expect my clients to have issues getting this started, but once you wrap your head around the way Lastpass works, I think you'll rest a little easier knowing that your accounts are safer.<br />
<br />
If you are in need of a security refresher, feel free to contact me at damon@damonbreeden.com or message 419-210-3631 (US) or 099-033-0345 (EC, Whatsapp).Damonhttp://www.blogger.com/profile/10571355989481357458noreply@blogger.com0