In a previous blog post we looked in to the wonderful reverse engineered world of Samba. We saw the practical uses and how this piece of open source software with a hilarious and inspiring backstory can be utilised in almost any environment.
In this post we are going to get down and dirty with some code and see how we can automate the build of this paradigm.
Automate ALL the Things!
DevSecOps is all about automation, we can use the same tooling as the DevOps crowd are using and either employ them in the same way, or as this blog develops it will prove how we can use them in our own way to satisfy our goals within the environment.
Some tools that we can use to provision the required packages and configuration on to our instances are below, each will look at how we can quickly get set up and running!
Ansible
Ansible is a fun little project, using SSH keys to communicate with Linux servers and now has support for running on more modern Windows instances.
Ansible has a wide array of preconfigured roles that the community has worked on and most of the time we can use galaxy to find and reuse there roles that will do a lot of the heavy lifting for us, allowing us to work smarter and not harder.
We can create a playbook to configure samba with the following yaml:
---- name: Install and configure Sambahosts: allbecome: truetasks:- name: Install the Samba packagepackage:name: sambastate: present- name: Create the Samba configuration filetemplate:src: samba.conf.j2dest: /etc/samba/samba.conf- name: Start and enable the Samba serviceservice:name: smbdstate: startedenabled: true
Of course most of the configuration would come from the samba configuration file which is in the jinja2 format:
[global]workgroup = {{ samba_workgroup }}server string = {{ samba_server_string }}security = {{ samba_security }}[{{ samba_share_name }}]path = {{ samba_share_path }}valid users = {{ samba_valid_users }}read only = {{ samba_readonly }}
With our example configuration we will need to add some variables:
samba_workgroup: MYGROUPsamba_server_string: Samba Serversamba_security: usersamba_share_name: mysharesamba_share_path: /mnt/sharedsamba_valid_users: user1,user2samba_readonly: no
You can run this playbook by using the ansible-playbook command and specifying the playbook file. For example, ansible-playbook install_samba.yml
Cinc/Chef
With any given chef cookbook you can get most of the files that you will need to roll your own cookbook with:
$ chef generate cookbook samba
From this skeleton you can add to the default.rb recipe:
# samba/recipes/default.rbpackage 'samba' doaction :installendtemplate '/etc/samba/samba.conf' dosource 'samba.conf.erb'owner 'root'group 'root'mode '0644'variables(workgroup: 'MYGROUP',server_string: 'Samba Server',security: 'user',share_name: 'myshare',share_path: '/mnt/shared',valid_users: 'user1,user2',readonly: 'no')endservice 'smbd' doaction [:enable, :start]end
which provides the instruction to install the software, on to the configuration. Stored under the templates directory we can add the following, in erb this time:
# samba/templates/default/samba.conf.erb
[global]workgroup = <%= @workgroup %>server string = <%= @server_string %>security = <%= @security %>[<%= @share_name %>]path = <%= @share_path %>valid users = <%= @valid_users %>read only = <%= @readonly %>
The variables are stored in the recipe (hardcoding is not great!) but in practice these could be applied to node variables or stored on the chef server alongside the deploy. If we wanted to break these out we could store them as attributes:
# samba/attributes/default.rbdefault['samba']['workgroup'] = 'MYGROUP'default['samba']['server_string'] = 'Samba Server'default['samba']['security'] = 'user'default['samba']['share_name'] = 'myshare'default['samba']['share_path'] = '/mnt/shared'default['samba']['valid_users'] = 'user1,user2'default['samba']['readonly'] = 'no'
And update our recipe to use the variables instead.
Puppet
Much the same with puppet we can begin our module with:
$ puppet module generate myorg-samba
to get the boiler plate code for our module, we can then create a class within our module to contain all the installation logic:
# myorg-samba/manifests/init.pp
class samba {
package { 'samba':
ensure => installed,
}
file { '/etc/samba/samba.conf':
ensure => file,
content => template('myorg-samba/samba.conf.erb'),
owner => 'root',
group => 'root',
mode => '0644',
}
service { 'smbd':
ensure => running,
enable => true,
}
}
and then our template which is once again in the erb format:
# myorg-samba/templates/samba.conf.erb[global]workgroup = <%= @workgroup %>server string = <%= @server_string %>security = <%= @security %>[<%= @share_name %>]path = <%= @share_path %>valid users = <%= @valid_users %>read only = <%= @readonly %>
and finally we can register our class and module either in a manifest to instantiate the instance with a .pp file or the puppet master file hold these variables and provide them to the requesting instance.
That's the server what about domain joining?
Most of the connection should happen automatically, in a Cloud Environment we will need to go from a bare Operating System to a provisioned and connected one, this is where can use the Cloud Providers "Boot Strap" mechanism that will allow us to kick off one of the above Provisioners to get our instance configured and connected into the Network.
AWS
Most of the time you will be using samba on a Linux instance, in AWS you can bootstrap your instance with userdata. With a server now automated and running you can provide the following to your AWS EC2 instance:
#!/bin/bash# Install the necessary packagesyum install -y samba-client samba-common-tools# Join the domainnet ads join -U Administrator -S samba.example.com# Create a credentials file for use with the 'net' commandecho "username=Administrator" > /etc/samba/credentialsecho "password=mypassword" >> /etc/samba/credentialschmod 600 /etc/samba/credentials# Configure the system to use the credentials fileecho "session required pam_mkhomedir.so skel=/etc/skel/ umask=0022" >> /etc/pam.d/common-sessionecho "session required pam_unix.so" >> /etc/pam.d/common-sessionecho "session optional pam_winbind.so" >> /etc/pam.d/common-sessionecho "session required pam_winbind.so use_first_pass" >> /etc/pam.d/common-sessionecho "auth required pam_winbind.so use_first_pass" >> /etc/pam.d/common-authecho "account required pam_winbind.so" >> /etc/pam.d/common-account# Configure the system to use winbind for authenticationecho "winbind use default domain = yes" >> /etc/samba/smb.confecho "winbind offline logon = yes" >> /etc/samba/smb.confecho "winbind enum users = yes" >> /etc/samba/smb.confecho "winbind enum groups = yes" >> /etc/samba/smb.conf# Start and enable the winbind servicesystemctl start winbindsystemctl enable winbind
Which would enable you to bind to the domain
Azure
In Azure you have a similar concept of userdata but they call it custom script extension. Taking the above mentioned script you can save it into Azure and run the following with your instances.
# Attach the custom script extension to the VMaz vm extension set --resource-group myResourceGroup --vm-name myVm --name customScript --publisher Microsoft.Azure.Extensions --settings '{"fileUris":["https://raw.githubusercontent.com/myorg/samba-join-script/master/join_samba_domain.sh"],"commandToExecute":"sh join_samba_domain.sh"}'
GCP
GCP again is similar to AWS as you can provide this user data to the fleet on start up with a start up script, the following will create a VM in AWS that uses this data.
gcloud compute instances create my-vm \--project=my-project \--zone=us-central1-a \--metadata-from-file startup-script=join_samba_domain.sh
Docker/K8s
I would not suggest this as a usecase as I think this level of virtualisation is beyond identity, we should not be authenticating to containers, but for demo purposes and completeness lets make a Dockerfile.
FROM ubuntu:latestRUN apt-get update && apt-get install -y samba-client samba-common-toolsRUN echo "username=Administrator" > /etc/samba/credentialsRUN echo "password=mypassword" >> /etc/samba/credentialsRUN chmod 600 /etc/samba/credentialsRUN echo 'session required pam_mkhomedir.so skel=/etc/skel/ umask=0022' >> /etc/pam.d/common-sessionRUN echo 'session required pam_unix.so' >> /etc/pam.d/common-sessionRUN echo 'session optional pam_winbind.so' >> /etc/pam.d/common-sessionRUN echo 'session required pam_winbind.so use_first_pass' >> /etc/pam.d/common-sessionRUN echo 'auth required pam_winbind.so use_first_pass' >> /etc/pam.d/common-authRUN echo 'account required pam_winbind.so' >> /etc/pam.d/common-accountRUN echo "winbind use default domain = yes" >> /etc/samba/smb.conf
Now you know how to dance the Samba!
In the previous blog we looked at the installation of Samba and it's basic configuration, now with this blog post we can automate and scale that paradigm to much larger operations. As well as this we can look into using other automatic methods of provisioning our users, we can integrate the Samba instance with PAM and use a wider array of LDAP type products to enable automated syncing of our passwds.
We can even change things up to make the passwd command redundant in favour of the smbpasswd command to update our details across the network.
Assigning permissions to these shares can be tricky when you are working with big organization with multiple groups and users. In such case you can check /etc/samba/smb.conf.example for some sample configurations or the man page of smb.conf for a clear understanding
Wrap Up!
Hopefully with the above you can see how easy it is to set up your own Domain with centralised control and how you can use it to administrate your network (home and enterprise) much easier!