[MODULES-9578] sshkeys_core : Cannot create ssh_authorized_key file in custom directory. Created: 2019/07/23  Updated: 2020/03/03  Resolved: 2019/10/24

Status: Resolved
Project: Modules
Component/s: sshkeys_core
Affects Version/s: None
Fix Version/s: None

Type: Bug Priority: Normal
Reporter: Robert August Vincent II Assignee: Gabriel Nagy
Resolution: Fixed Votes: 2
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified
Environment:

Relevant $facts:

{
    "fips_enabled": true,
    "hypervisors": {
      "vmware": {
        "version": "ESXi 6.5"
      }
    },
    "is_virtual": true,
    "java_default_home": "/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-7.b13.el7.x86_64",
    "java_libjvm_path": "/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-7.b13.el7.x86_64/jre/lib/amd64/server",
    "java_major_version": "8",
    "java_patch_level": "181",
    "java_version": "1.8.0_181",
    "kernel": "Linux",
    "kernelmajversion": "3.10",
    "kernelrelease": "3.10.0-957.el7.x86_64",
    "kernelversion": "3.10.0",
    "lsbdistcodename": "Core",
    "lsbdistdescription": "CentOS Linux release 7.6.1810 (Core)",
    "lsbdistid": "CentOS",
    "lsbdistrelease": "7.6.1810",
    "lsbmajdistrelease": "7",
    "lsbminordistrelease": "6",
    "lsbrelease": ":core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch",
    "openssh_version": "7.4",
    "operatingsystem": "CentOS",
    "operatingsystemmajrelease": "7",
    "operatingsystemrelease": "7.6.1810",
    "os": {
      "architecture": "x86_64",
      "distro": {
        "codename": "Core",
        "description": "CentOS Linux release 7.6.1810 (Core)",
        "id": "CentOS",
        "release": {
          "full": "7.6.1810",
          "major": "7",
          "minor": "6"
        },
        "specification": ":core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch"
      },
      "family": "RedHat",
      "hardware": "x86_64",
      "name": "CentOS",
      "release": {
        "full": "7.6.1810",
        "major": "7",
        "minor": "6"
      },
      "selinux": {
        "config_mode": "enforcing",
        "config_policy": "targeted",
        "current_mode": "enforcing",
        "enabled": true,
        "enforced": true,
        "policy_version": "31"
      }
    },
    "osfamily": "RedHat",
    "package_provider": "yum",
    "productname": "VMware Virtual Platform",
    "puppet_client_datadir": "/opt/puppetlabs/puppet/cache/client_data",
    "puppet_confdir": "/etc/puppetlabs/puppet",
    "puppet_config": "/etc/puppetlabs/puppet/puppet.conf",
    "puppet_environmentpath": "/etc/puppetlabs/code/environments",
    "puppet_master_server": "cusdpupcse02.internal.cnngad.com",
    "puppet_ruby_dir": "/opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet",
    "puppet_server": "cusdpupcse02.internal.cnngad.com",
    "puppet_service_enabled": false,
    "puppet_service_started": false,
    "puppet_settings": {
      "main": {
        "confdir": "/etc/puppetlabs/puppet",
        "codedir": "/etc/puppetlabs/code",
        "vardir": "/opt/puppetlabs/puppet/cache",
        "name": "facts",
        "logdir": "/var/log/puppetlabs/puppet",
        "log_level": "notice",
        "disable_warnings": "[]",
        "strict": "warning",
        "disable_i18n": "false",
        "priority": "",
        "trace": "false",
        "profile": "false",
        "future_features": "false",
        "static_catalogs": "true",
        "strict_environment_mode": "false",
        "autoflush": "true",
        "syslogfacility": "local6",
        "statedir": "/opt/puppetlabs/puppet/cache/state",
        "rundir": "/var/run/puppetlabs",
        "genconfig": "false",
        "genmanifest": "false",
        "color": "ansi",
        "mkusers": "false",
        "manage_internal_file_permissions": "true",
        "onetime": "false",
        "path": "none",
        "libdir": "/opt/puppetlabs/puppet/cache/lib",
        "environment": "production",
        "environmentpath": "/etc/puppetlabs/code/environments",
        "always_retry_plugins": "true",
        "diff_args": "-u",
        "diff": "diff",
        "show_diff": "false",
        "daemonize": "true",
        "maximum_uid": "4294967290",
        "route_file": "/etc/puppetlabs/puppet/routes.yaml",
        "node_terminus": "plain",
        "node_cache_terminus": "",
        "data_binding_terminus": "hiera",
        "hiera_config": "/etc/puppetlabs/puppet/hiera.yaml",
        "binder_config": "",
        "catalog_terminus": "compiler",
        "catalog_cache_terminus": "",
        "facts_terminus": "facter",
        "default_file_terminus": "rest",
        "http_proxy_host": "none",
        "http_proxy_port": "3128",
        "http_proxy_user": "none",
        "http_proxy_password": "none",
        "http_keepalive_timeout": "4",
        "http_debug": "false",
        "http_connect_timeout": "120",
        "http_read_timeout": "600",
        "http_user_agent": "Puppet/6.6.0 Ruby/2.5.3-p105 (x86_64-linux)",
        "filetimeout": "15",
        "environment_timeout": "0",
        "environment_data_provider": "",
        "prerun_command": "",
        "postrun_command": "",
        "freeze_main": "false",
        "preview_outputdir": "/opt/puppetlabs/puppet/cache/preview",
        "dns_alt_names": "",
        "csr_attributes": "/etc/puppetlabs/puppet/csr_attributes.yaml",
        "certdir": "/etc/puppetlabs/puppet/ssl/certs",
        "ssldir": "/etc/puppetlabs/puppet/ssl",
        "ssl_lockfile": "/etc/puppetlabs/puppet/ssl/ssl.lock",
        "publickeydir": "/etc/puppetlabs/puppet/ssl/public_keys",
        "requestdir": "/etc/puppetlabs/puppet/ssl/certificate_requests",
        "privatekeydir": "/etc/puppetlabs/puppet/ssl/private_keys",
        "privatedir": "/etc/puppetlabs/puppet/ssl/private",
        "passfile": "/etc/puppetlabs/puppet/ssl/private/password",
        "hostcert": "/etc/puppetlabs/puppet/ssl/certs/cusdpupcse02.internal.cnngad.com.pem",
        "hostprivkey": "/etc/puppetlabs/puppet/ssl/private_keys/cusdpupcse02.internal.cnngad.com.pem",
        "hostpubkey": "/etc/puppetlabs/puppet/ssl/public_keys/cusdpupcse02.internal.cnngad.com.pem",
        "localcacert": "/etc/puppetlabs/puppet/ssl/certs/ca.pem",
        "ssl_client_ca_auth": "",
        "ssl_server_ca_auth": "",
        "hostcrl": "/etc/puppetlabs/puppet/ssl/crl.pem",
        "certificate_revocation": "chain",
        "key_type": "rsa",
        "named_curve": "prime256v1",
        "digest_algorithm": "sha256",
        "supported_checksum_types": "[\"sha256\", \"sha384\", \"sha512\", \"sha224\"]",
        "logdest": "",
        "plugindest": "/opt/puppetlabs/puppet/cache/lib",
        "pluginsource": "puppet:///plugins",
        "pluginfactdest": "/opt/puppetlabs/puppet/cache/facts.d",
        "pluginfactsource": "puppet:///pluginfacts",
        "localedest": "/opt/puppetlabs/puppet/cache/locales",
        "localesource": "puppet:///locales",
        "pluginsignore": ".svn CVS .git .hg",
        "factpath": "/opt/puppetlabs/puppet/cache/lib/facter:/opt/puppetlabs/puppet/cache/facts",
        "external_nodes": "none",
        "rich_data": "true"
      },
    "puppet_stringify_facts": false,
    "puppet_vardir": "/opt/puppetlabs/puppet/cache",
    "puppetserver_jruby": {
      "dir": "/opt/puppetlabs/server/apps/puppetserver",
      "jarfiles": [
        "puppet-server-release.jar"
      ]
    },
    "puppetversion": "6.6.0",
    "python2_release": "2.7",
    "python2_version": "2.7.5",
    "python3_release": "3.6",
    "python3_version": "3.6.8",
    "python_release": "2.7",
    "python_version": "2.7.5",
    "ruby": {
      "platform": "x86_64-linux",
      "sitedir": "/opt/puppetlabs/puppet/lib/ruby/site_ruby/2.5.0",
      "version": "2.5.3"
    },
    "rubyplatform": "x86_64-linux",
    "rubysitedir": "/opt/puppetlabs/puppet/lib/ruby/site_ruby/2.5.0",
    "rubyversion": "2.5.3",
    "service_provider": "systemd",
    "systemd": true,
    "systemd_internal_services": {
      "systemd-bootchart.service": "disabled",
      "systemd-nspawn@.service": "disabled",
      "systemd-readahead-collect.service": "enabled",
      "systemd-readahead-done.service": "indirect",
      "systemd-readahead-drop.service": "enabled",
      "systemd-readahead-replay.service": "enabled"
    },
    "systemd_version": "219",
    "virtual": "vmware",
    "virtualenv_version": "16.6.2",
    "clientversion": "6.6.0",
}


Issue Links:
Duplicate
duplicates PUP-9812 With "root" account, Puppet "ssh_auth... Closed
Template: MODULES Bug Template
Team: Night's Watch
Story Points: 3
Sprint: NW - 2019-08-21, NW - 2019-09-03, NW - 2019-09-18, NW - 2019-10-02, NW - 2019-10-16, NW - 2019-10-30
Method Found: Needs Assessment
Release Notes: New Feature
Release Notes Summary: This feature adds a new parameter, `drop_privileges` which when set to false allows the module to write a ssh_authorized_key file in a privileged path. Due to the possible security implications of this, the parameter must be manually specified in order to activate this functionality.
    
A path is considered to be privileged/trusted if all of its ancestors:
    - do not contain any symlinks
    - have the same owner as the user who runs Puppet
    - are not world/group writable
QA Risk Assessment: Needs Assessment

 Description   

Basic Info
Module Version: master
Puppet Version: 6.6.0
OS Name/Version: CentOS Linux release 7.6.1810 (Core)

The ssh_authorized_key resource cannot create a keyfile in the /etc/ssh/local_keys folder, which is owned by `root`.

# puppet apply -e "ssh_authorized_key { 'robvin': ensure => 'present', name => 'robert.vincent@conning.com', user => 'robvin', key => 'AAAAB3NzaC1yc2EAAA
ADAQABAAACAQDaiH2dkXP8UPvHnMKcKtmf9bETx8efi1+UrqWJxhjhb5XxneggyquJqvJlsS548qrTHnePFFcuTAuE7aPz3jfLp0RLJ6KRZyTqShyzQvBSecGoEvJoeyF3BrJL/sLVEa92ijG7CLM8dlVkvyicgLkemX6KrYo8neCKYFSPi2xIhGZx5SddUrVwM5arpB2t4Hn9sy6y2FVbraqt9q34133GeCIe6NqhGvtHVGJExemtMtzdOE7NaLbmwK4j5+u9Yip8zB20rF05jdH8IVa0TnZUTuvAxrHgK/y7l6lS7+Q2SAbEAjtbPxvD1Fwo+H0nC5dN5JEQT3xEbypc23fyxVmz029SpefJ6ZrDMDrHxN5RLJyvLfaFMzjywelmx17uhG4jKqbgGdpvXSyrxPM8QprDb2/2YAqY0D67L2S4m0iiaFSRmjXTVDlHmOY1d6QEWOfS4J6PcXrgUzTTINsPrMSBzWr2Bb9dIFbp87lvwbJTFTtYc716qVZwQng/YDsCn2hHeuBwGEAdjCeB72N+F2upPHNcNfP82fH+JalL8KpxcFUm9wPbfFJLjrdMdj5kBQkc25hnfWdsqKtLEqck+mfVWZIifEB3Ye/SWPfWhdmd5lIEcM7BR30ynMIGcLo3vcCcYZSdcT0DC5cAf40xy2tkf/xXLwNC4KpUoD8stwbqLQ==', type=>'ssh-rsa', target=>'/etc/ssh/local_keys/robvin' }"
Notice: Compiled catalog for cusdpupcse02.internal.cnngad.com in environment production in 0.11 seconds
Notice: /Stage[main]/Main/Ssh_authorized_key[robvin]/ensure: created
Error: Puppet::Util::FileType::FileTypeFlat could not write /etc/ssh/local_keys/robvin: Permission denied @ rb_sysopen - /etc/ssh/local_keys/robvin
Error: /Stage[main]/Main/Ssh_authorized_key[robvin]: Could not evaluate: Puppet::Util::FileType::FileTypeFlat could not write /etc/ssh/local_keys/robvin: Permission denied @ rb_sysopen - /etc/ssh/local_keys/robvin
Notice: Applied catalog in 0.64 seconds

It will add a key to an existing empty file:

[root@cusdpupcse02 ~]# touch /etc/ssh/local_keys/robvin
[root@cusdpupcse02 ~]# chown robvin:robvin  /etc/ssh/local_keys/robvin
[root@cusdpupcse02 ~]# puppet apply -e "ssh_authorized_key { 'robvin': ensure => 'present', name => 'robert.vincent@conning.com', user => 'robvin', key => 'AAAAB3NzaC1yc2EAAAADAQABAAACAQDaiH2dkXP8UPvHnMKcKtmf9bETx8efi1+UrqWJxhjhb5XxneggyquJqvJlsS548qrTHnePFFcuTAuE7aPz3jfLp0RLJ6KRZyTqShyzQvBSecGoEvJoeyF3BrJL/sLVEa92ijG7CLM8dlVkvyicgLkemX6KrYo8neCKYFSPi2xIhGZx5SddUrVwM5arpB2t4Hn9sy6y2FVbraqt9q34133GeCIe6NqhGvtHVGJExemtMtzdOE7NaLbmwK4j5+u9Yip8zB20rF05jdH8IVa0TnZUTuvAxrHgK/y7l6lS7+Q2SAbEAjtbPxvD1Fwo+H0nC5dN5JEQT3xEbypc23fyxVmz029SpefJ6ZrDMDrHxN5RLJyvLfaFMzjywelmx17uhG4jKqbgGdpvXSyrxPM8QprDb2/2YAqY0D67L2S4m0iiaFSRmjXTVDlHmOY1d6QEWOfS4J6PcXrgUzTTINsPrMSBzWr2Bb9dIFbp87lvwbJTFTtYc716qVZwQng/YDsCn2hHeuBwGEAdjCeB72N+F2upPHNcNfP82fH+JalL8KpxcFUm9wPbfFJLjrdMdj5kBQkc25hnfWdsqKtLEqck+mfVWZIifEB3Ye/SWPfWhdmd5lIEcM7BR30ynMIGcLo3vcCcYZSdcT0DC5cAf40xy2tkf/xXLwNC4KpUoD8stwbqLQ==', type=>'ssh-rsa', target=>'/etc/ssh/local_keys/robvin' }"
Notice: Compiled catalog for cusdpupcse02.internal.cnngad.com in environment production in 0.09 seconds
Notice: /Stage[main]/Main/Ssh_authorized_key[robvin]/ensure: created
Notice: Applied catalog in 2.82 seconds

Desired Behavior:

The ssh_authorized_key resource should create the file, if necessary.

Actual Behavior:

The ssh_authorized_key resource fails unless the target file already exists.



 Comments   
Comment by Trevor Vaughan [ 2019/07/23 ]

The fact that you need to create the target file does not appear to be documented at https://puppet.com/docs/puppet/5.5/types/ssh_authorized_key.html and is VERY unexpected behavior.

Comment by Robert August Vincent II [ 2019/07/25 ]

Using the following workaround for now, where each $user's authorized_keys file is in $ssh_local_keys/$user:

  # Workaround for MODULES-9578:
  exec { "Copy authorized keys to ${ssh_local_keys}":
    command     => @("EOC"/L$),
      getent passwd | cut -d: -f1,6 | xargs sh -c '
        while test "\$1"
        do
          h=\${1##*:}
          u=\${1%%:*}
          k=\$h/.ssh/authorized_keys
          l=$ssh_local_keys/\$u
          test -f \$k && cp -Z \$h \$l
          shift
        done'
      |EOC
    provider    => 'shell',
    refreshonly => true,
    require     => File[$ssh_local_keys],
  }
  Ssh_authorized_key<| target != undef |> ~> Exec["Copy authorized keys to ${ssh_local_keys}"]

Comment by Martin Alfke [ 2019/08/08 ]

Storing ssh authorized key files in a place where a user can not modify the content is an often used setup.

Working with Puppet 6.0.4 using my latest PR from puppetlabs-accounts module.

Module creates the file first using file resource and later adds the keys to the existing file.

https://github.com/puppetlabs/puppetlabs-accounts/blob/master/manifests/key_management.pp#L54

Comment by Gabriel Nagy [ 2019/08/12 ]

Trevor Vaughan, you do not need to create the target file. However, in this case, the parent directory is owned by root/another user and that's not how the module operates. It switches context to the $user, then creates the directory/file as needed.

     Puppet::Util::SUIDManager.asuser(@resource.should(:user)) do
      unless Puppet::FileSystem.exist?(dir = File.dirname(target))
        Puppet.debug "Creating #{dir} as #{@resource.should(:user)}"
        Dir.mkdir(dir, dir_perm)
      end
 
      super
 
      File.chmod(file_perm, target)
    end

When it works, it works because the file already exists and is owned by the correct user.

Comment by Trevor Vaughan [ 2019/08/12 ]

Gabriel Nagy Yeah, I got there eventually. That said, I believe that this behavior is a bug given the expectations of most users for things to "just work" and is not documented as such as far as I can tell.

Comment by Gabriel Nagy [ 2019/08/12 ]

Robert August Vincent II: in this case we can create everything as root and chown (what? file/parent directory/parent directory of parent directory) to the target user.

In your use case only the file had to be created (the parent directory was already there) so it should be as simple as creating the file as root and then changing ownership. But what about when the parent directory doesn't exist either? Should we care about the ownership of the parent directory, or just the file?

Comment by Martin Alfke [ 2019/08/12 ]

thia type should not create missing directory structure. this is what dirtree was made for.

Comment by Trevor Vaughan [ 2019/08/12 ]
  1. Create the target file and ensure that it has the correct owner permissions
  2. Use the existing code to add the key into the file

EDIT: AKA What Martin Alfke said and how the current File resource works.

Generated at Tue Jul 14 04:11:31 PDT 2020 using Jira 8.5.2#805002-sha1:a66f9354b9e12ac788984e5d84669c903a370049.