Managing a user password for linux in puppet - linux

I need to create a test user with a password using puppet.
I've read that puppet cannot manage user passwords in a generic cross-platform way, which is a pity.
I am doing this for Red Hat Enterprise Linux Server release 6.3.
I do as follows:
user { 'test_user':
ensure => present,
password => sha1('hello'),
}
puppet updates the password of the user,
but Linux says login/pwd incorrect when I try to log in.
It works (I can login) if I set the password manually in Linux with sudo passwd test_user, and then look at /etc/shadow and hardcode that value in puppet. something like:
user { 'test_user':
ensure => present,
password => '$1$zi13KdCr$zJvdWm5h552P8b34AjxO11',
}
I've tried also by adding $1$ in front of the sha1('hello'),
but it does not work either (note, $1$ stands for sha1).
How to modify the first example to make it work (using the plaintext password in the puppet file)?
P.S.: I am aware that I should use LDAP, or sshkeys, or something else, instead of hardcoding the user passwords in the puppet file. however, I am doing this only for running a puppet vagrant test, so it is ok to hardcode the user password.

Linux users have their passwords stored as hash in /etc/shadow file. Puppet passes the password supplied in the user type definition in the /etc/shadow file.
Generate your hash password using openssl command:
#openssl passwd -1
#Enter your password here
Password:
Verifying - Password:
$1$HTQUGYUGYUGwsxQxCp3F/nGc4DCYM
The previous example generate this hash:
$1$HTQUGYUGYUGwsxQxCp3F/nGc4DCYM/
Add this hash password to your class as shown (do not forget the quotes)
user { 'test_user':
ensure => present,
password => '$1$HTQUGYUGYUGwsxQxCp3F/nGc4DCYM/',
}

The stdlib package of puppetlabs implements a similar pw_hash function of the accepted answer.
Be sure to add the library to your configuration. If you use librarian, just add in your Puppetfile
mod 'puppetlabs-stdlib'
Then to create an user, simply :
user { 'user':
ensure => present,
password => pw_hash('password', 'SHA-512', 'mysalt'),
}

The sha1 function in puppet is not directly intended for passwd entries, as you figured out.
I'd say setting the hash rather than the password is good practice! You are not really supposed to be able to recover a password anyway - you can generate it once, or you can have puppet generate it every time - generating that hash once should be enough IMHO...
You can generate a password on Debian/Ubuntu like this:
pwgen -s -1 | mkpasswd -m sha-512 -s
...on CentOS you can use some grub-crypt command instead of mkpasswd...

I had success (gist) with ruby's String#crypt method from within a Puppet parser function.
AFAICS it's using the crypt libc functions (see: info crypt), and takes the same arguments $n$[rounds=<m>$]salt, where n is the hashing function ($6 for SHA-512) and m is the number of key strengthening rounds (5000 by default).

You can use the generate function to let Puppet create the hash for you:
$password = 'hello'
user { 'test_user':
ensure => 'present',
password => generate('/bin/sh', '-c', "mkpasswd -m sha-512 ${password} | tr -d '\n'"),
}

Puppet: user with a SHA 512 hashed password
I came up with a method that doesn't need anything to add if you have python 2.6. I tested this on puppet 3.6.2 on CentOS 6.4:
$pass="password"
$shatag="\$6\$"
$cmd="import crypt, base64, os, sys; sys.stdout.write(crypt.crypt('$pass', '$shatag' + base64.b64encode(os.urandom(16))[:8]))"
user { 'boop':
ensure => present,
password => generate ("/usr/bin/python", "-c", $cmd),
}
Explanations
the sha tag is here to specify to crypt the hash method we want:
6 is the type of hash for SHA-512
$1$ -> MD5
$2a$ -> Blowfish (not in mainline glibc; added in some Linux distributions)
$5$ -> SHA-256 (since glibc 2.7)
$6$ -> SHA-512 (since glibc 2.7)
thx davey and wiki_crypt
sys.stdout.write is here to avoid '\n' of print
base64.b64encode(os.urandom(16))[:8]):
os.urandom(16) create a 16 bits long binary string
base64.b64encode encode this string in base64
[:8] take the first 8 characters of this string (as base64 encoding length may vary)
generate is a puppet function that create text when on the puppet master.
You can't use this function like you want because it is 'protected' ê.é (last post suggest a workaround to this protection-or-whatever)
hth

In my Vagrantfile, I did this:
$newuserid = ENV["USERNAME"]
config.vm.provision :puppet do |puppet|
puppet.module_path = "modules"
puppet.manifests_path = "manifests"
puppet.manifest_file = "main.pp"
puppet.facter = {"newuserid" => $newuserid}
puppet.options = "--verbose"
end
And in my main.pp file:
user { $newuserid :
ensure => present,
home => "/home/${newuserid}",
managehome => true,
gid => "mygid",
}
exec { 'set password':
command => "/bin/echo \"${newuserid}:${newuserid}\" | /usr/sbin/chpasswd",
require => User [ $newuserid ],
}

just generate encrypted password from grub-crypt --sha-512 and paste

Related

Paramiko expect sends a command before the expected output is received

I'm trying to configure routers with a python script and wanted to use paramiko expect to do the job. The function change_password is part of a class and contains the following paramiko-expect-lines (with self.ssh_client as an instance of paramiko.SSHClient):
with SSHClientInteraction(self.ssh_client, timeout=5, display=True) as self.interact:
# change password
prompt = '.*root#cb_park:~#.*'
self.interact.expect(prompt)
self.interact.send('passwd')
self.interact.expect(['.*Changing password for root.*', '.*New password:.*'])
self.interact.send('NewPassword')
self.interact.expect('.*Retype password:.*')
self.interact.send('NewPassword')
self.interact.expect(['.*passwd: password for root changed by root.*', prompt])
# modify firstlogin
self.interact.send('uci delete vuci.main.firstlogin')
self.interact.expect(prompt)
self.interact.send('uci commit')
self.interact.expect(prompt)
However, sometimes the send command is executed before the prompt appears. For example in the following scenario:
root#cb_park:~# passwd
Changing password for root
New password:
Retype password:
passwd: password for root changed by root
root#cb_park:~# uci delete vuci.main.firstlogin
uci commit
uci: Entry not found
root#cb_park:~# uci commit
'uci commit' is being executed, even though the expected prompt has not been given out. Sometimes this even happens on the second line and the new password is executed right after 'Changing password for root' even though 'New password:' has not been given out which leads to a timeout right after:
root#cb_park:~# passwd
Changing password for root
NewPassword
New password: EXCESS TIME RECV_READY TIMEOUT, did you expect() before a send()
Retype password: EXCESS TIME RECV_READY TIMEOUT, did you expect() before a send()
Passwords don't match
passwd: password for root is unchanged
root#cb_park:~#
I don't really know what the error could be or how to debug. Any idea?
paramiko 2.8.0
paramiko-expect 0.3.0
Python 3.8.10
Cheers Ian
in case anybody has a similar problem:
I took out the multiline expects and replaced it by only expecting the last line. I guess I missunderstood the docs and thought I'd have to expect both lines. New code as follows works without timeout.
with SSHClientInteraction(self.ssh_client, timeout=5, display=True) as self.interact:
# change password
prompt = '.*root#cb_park:~#.*'
self.interact.expect(prompt)
self.interact.send('passwd')
self.interact.expect('.*New password:.*') # instead of self.interact.expect(['.*Changing password for root.*', '.*New password:.*'])
self.interact.send('NewPassword')
self.interact.expect('.*Retype password:.*')
self.interact.send('NewPassword')
self.interact.expect(prompt) # instead of self.interact.expect(['.*passwd: password for root changed by root.*', prompt])
# modify firstlogin
self.interact.send('uci delete vuci.main.firstlogin')
self.interact.expect(prompt)
self.interact.send('uci commit')
self.interact.expect(prompt)
Ian

Create Database from AWS CLI without providing a password

is there an elegant way to create a RDS instance without providing the credentials in plain text?
I already created a secret, but I can't find any documentation about how to use a secret when creating a database.
My current solution is saving the password in a file and reading it into a variable:
$ vim password.txt
$ pw=$(cat password.txt)
$ aws rds create-db-instance \
# all the arguments \
--master-user-password $pw
Update: I figured another way:
$ read -s -p "enter password: " pw
$ aws rds ... --master-user-password $pw
openejb-core jar is a library that can be used to cipher and decipher text. You can use this library to cipher the actual password and store the ciphered password in the text file 'password.txt'.
While retrieving the password into 'pw' variable, you can decipher it again and use it further. Hence, the actual password is never visible, even with root access.

Accessing password from a file in puppet for use in exec

I want to run a command in open source puppet to activate a Unity3D license, but I don't want the serial or the password in my git repo:
exec { 'license-unity':
command => '/opt/Unity/Editor/Unity -batchmode -nographics -serial A1-ABCD-1234-ETC -username my.user#a.b.ca -password myPassword -quit'
subscribe => Package['UnityEditor'],
refreshonly => true,
}
How can I read the serial# and the password from a file (either on the puppetserver or on the node) and substitute it in the command?
For example, if I had a file on the puppet server called .secret owned by root and perms 400. How do I read the content into a variable for use in the puppet manifest?
There are two standard ways to achieve this given the route you prefer:
Use the file function. This is for masterless Puppet or the file is hosted on the Puppet Master.
# using the module path instead of the absolute path would end up storing your secret in git, which is what you are trying to avoid
$password = file('/absolute/path/to/.secret')
exec { 'license-unity':
command => "/opt/Unity/Editor/Unity -batchmode -nographics -serial A1-ABCD-1234-ETC -username my.user#a.b.ca -password $password -quit"
subscribe => Package['UnityEditor'],
refreshonly => true,
}
Doc: https://puppet.com/docs/puppet/5.3/function.html#file
Corollary: If you need to do some kind of parsing on the file, such as if it is more than a text file with the password inside, then you can use a custom function with the modern Ruby API. https://puppet.com/docs/puppet/5.3/functions_ruby_overview.html. Let me know if this is the case.
Use a custom fact. This is for storing the file on the client in a Master/Client setup. Using an external fact would also end up storing the secret in git, which would present the problem you are attempting to avoid.
# module/lib/facter/password.rb
Facter.add(:password) do
setcode do
File.read('/absolute/path/to/.secret')
end
end
# manifest.pp
exec { 'license-unity':
command => "/opt/Unity/Editor/Unity -batchmode -nographics -serial A1-ABCD-1234-ETC -username my.user#a.b.ca -password $password -quit"
subscribe => Package['UnityEditor'],
refreshonly => true,
}
Doc: https://puppet.com/docs/facter/3.9/custom_facts.html#configuring-facts
Corollary: If you need to do some kind of parsing on the file, such as if it is more than a text file with the password inside, then you can use native Ruby classes and methods for that (i.e. JSON.parse or YAML.load_file if the file is in those formats).
Notable alternatives to the method you are pursuing include using Puppet to retrieve from a secrets management software, such as Vault, or using encryption/decryption algorithms, such as AES-256, to store the encrypted file in your SCM and then decrypt it during catalog compilation.

plink in PowerShell to run commands on Linux machine (SuSE)

I am trying to automate the process of adding extra storage in a linux machine. I'm using plink in PowerShell installed on my Windows machine.
Below is the code:
$plinkpath = "C:\Users\mydrive\Modules\plink.exe"
if (Test-Path $plinkpath) {
Set-Alias plink $plinkpath
} else {
throw "Plink.exe is reqruied"
}
$passw = "linuxadmin$123"
$commands = #(
"sudo su;",
"pvcreate /dev/sde;",
"vgcreate test_vog /dev/sde",
"lvcreate -l 100%FREE -n test_lev test_vog;",
"mkfs.ext3 /dev/test_vog/test_lev;",
"mkdir /azurenew;",
"echo ""/dev/test_vog/test_lev /azurenew/ ext3 defaults 1 1"" >> /etc/fstab;",
"mount /azurenew/;"
)
Approach 1: Using .ppk file
plink -ssh -i "C:\Users\amurthy\Documents\WindowsPowerShell\Modules\sshprivate.ppk" linuxadmin#xx.xx.xx.xxx $commands
In the above situation PowerShell hangs and no response on the console. Not sure what's happening.
Approach 2: using direct log in
plink -P "22" -v "linuxadmin#xx.xx.xx.xxx" -pw "linuxadmin$123" $commands
Here, I get below response on console
Using username "linuxadmin".
Sent password
Password authentication failed
I do not understand why the passoword authentication failed though I am able to login using putty.exe with that password.
Can anyone please help me here to solve my above automation problem? If you have any better solution altogether really welcome.
The password login attempt fails because you defined the password in a double-quoted string. PowerShell tries to expand the (undefined) variable $123 in linuxadmin$123, so you're actually passing just linuxadmin as the password. You could use a single-quoted string to avoid this, but public key authentication (your first approach) is the better approach anyway, so I recommend sticking with that.
I'm not sure why your first approach causes the console to hang, though. If the key were password-protected you should be prompted for the password. There's a semicolon missing at the end of "vgcreate test_vog /dev/sde", but if that caused an issue I'd expect to see an error message.
Try running plink with the parameter -v (verbose messages) to get a better picture of what's going on.

curl request does not accept a password containing "#" for a url with HTTP basic authentication

As we know that a curl request for a url that is protected by basic authentication can be made by using a format like this curl -v http://username: password#xyz.com . But i have observed that passing credentials like this only works if password does not have a "#" itself. For example If my request is curl -v http://user01:helloworld#xyz.com it will work . But if it is curl -v http://user01:hello#world#xyz.com , it fails . How can we make the first # to be understood as a password, as currently i think # is taken as a conventional delimiter before passing credentials, and therefore request gets confused as to what is the password . Any thoughts on this, or am i missing anything ?
Use -u which defaults to basic auth
-u, --user <user:password>
Specify the user name and password to use for server authentication. Overrides -n, --netrc and --netrc-
optional.
If you simply specify the user name, curl will prompt for a password.
The user name and passwords are split up on the first colon, which makes it impossible to use a colon in
the user name with this option. The password can, still.
Possibly even better would be to define your passwords in the netrc file and then avoid passwords on the command line altogether.

Resources