Github – Merge conflict and how to solve it

I recently contributed to one of Microsoft’s DSC resource modules via Github web interface (click on the file > edit > paste revised code > create pull request) as I’ve done countless times before. This time however I ran into a merge conflict that couldn’t be solved through the web site so I had to do it from the command line.

I contributed to the dev branch of the following repository and created a Pull Request:

  • Repository:
  • Branch: dev
  • Pull Request

There are countless guides on how to solve github merge conflicts but the ones I had a look at all presumed things that were missing in my case (see here or here).

So I clone my fork of the official xPSDesiredStateConfiguration repository and my default branch is dev:

PS D:\dsc> git clone
Cloning into 'xPSDesiredStateConfiguration'...
remote: Counting objects: 713, done.
Receiving objects:  96% (685/713)   0 (delta 0), pack-reused 713 eceiving objects:  95% (678/713)
Receiving objects: 100% (713/713), 261.82 KiB | 0 bytes/s, done.
Resolving deltas: 100% (398/398), done.
Checking connectivity... done.
PS D:\dsc> cd .\xPSDesiredStateConfiguration\
PS D:\dsc\xPSDesiredStateConfiguration> git branch
* dev

Fetching updates from the upstream repo is required to update our fork but it fails because there is no upstream repository configured:

PS D:\dsc\xPSDesiredStateConfiguration> git fetch upstream
fatal: 'upstream' does not appear to be a git repository
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

So I add Microsoft’s repo as remote upstream location (as you can see it’s listed in the git remote output) and update my fork’s dev branch with all the changes from the official repo’s dev branch:

PS D:\dsc\xPSDesiredStateConfiguration> git remote add upstream
PS D:\dsc\xPSDesiredStateConfiguration> git remote -v
origin (fetch)
origin (push)
upstream (fetch)
upstream (push)
PS D:\dsc\xPSDesiredStateConfiguration> git fetch upstream
remote: Counting objects: 38, done.
remote: Total 38 (delta 18), reused 18 (delta 18), pack-reused 20
Unpacking objects: 100% (38/38), done.
 * [new branch] TravisEz13-patch-1 -> upstream/TravisEz13-patch-1
 * [new branch] dev -> upstream/dev
 * [new branch] master -> upstream/master
 * [new tag] ->
PS D:\dsc\xPSDesiredStateConfiguration> git merge upstream/dev
Updating 171b484..e1cc5eb
 .../PublishModulesAndMofsToPullServer.psm1 | 167 +++++++++++++++++++++
 DSCPullServerSetup/ | Bin 0 -> 2482 bytes
 .../MSFT_xDSCWebService.Schema.mof | 1 -
 .../MSFT_xDSCWebService/MSFT_xDSCWebService.psm1 | 146 +++++++-----------
 .../PullServerSetupTests.ps1 | 135 +++++++++++++++++
 Examples/Sample_xDscWebService.ps1 | 14 +-
 Examples/Sample_xDscWebServiceRegistration.ps1 | 14 +-
 Examples/Sample_xDscWebServiceRemoval.ps1 | 7 - | 15 +-
 Tests/Integration/ | 36 ++---
 appveyor.yml | 4 +-
 xPSDesiredStateConfiguration.psd1 | 5 +-
 12 files changed, 384 insertions(+), 160 deletions(-)
 create mode 100644 DSCPullServerSetup/PublishModulesAndMofsToPullServer.psm1
 create mode 100644 DSCPullServerSetup/
 create mode 100644 Examples/PullServerDeploymentVerificationTest/PullServerSetupTests.ps1
PS D:\dsc\xPSDesiredStateConfiguration> ise .\DSCPullServerSetup\PublishModulesAndMofsToPullServer.psm1

But wait, something is wrong here – my changes aren’t visible in the ISE. Instead, the file seems to be the one from Microsoft’s dev branch. So where did my changes go?

It turns out that editing a file from a remote repository through the Github web interface creates an entirely new branch in your forked repository called patch-#. What you have to do is merge the upstream repo’s changes with that branch and not the dev branch of your fork. Then fix remaining merge conflicts manually, add the affected files and commit with a descriptive message before you push it to your fork.

PS D:\dsc\xPSDesiredStateConfiguration> git checkout patch-1
Branch patch-1 set up to track remote branch patch-1 from origin.
Switched to a new branch 'patch-1'
PS D:\dsc\xPSDesiredStateConfiguration> git merge upstream/dev
Auto-merging DSCPullServerSetup/PublishModulesAndMofsToPullServer.psm1
CONFLICT (content): Merge conflict in DSCPullServerSetup/PublishModulesAndMofsToPullServer.psm1
Automatic merge failed; fix conflicts and then commit the result.
PS D:\dsc\xPSDesiredStateConfiguration> ise DSCPullServerSetup/PublishModulesAndMofsToPullServer.psm1
PS D:\dsc\xPSDesiredStateConfiguration> git add DSCPullServerSetup/PublishModulesAndMofsToPullServer.psm1
PS D:\dsc\xPSDesiredStateConfiguration> git commit -m "Merged patch-1 fixed conflict."
[patch-1 c77509d] Merged patch-1 fixed conflict.
PS D:\dsc\xPSDesiredStateConfiguration> git branch
* patch-1
PS D:\dsc\xPSDesiredStateConfiguration> git status
On branch patch-1
Your branch is ahead of 'origin/patch-1' by 10 commits.
 (use "git push" to publish your local commits)
nothing to commit, working directory clean
PS D:\dsc\xPSDesiredStateConfiguration> git push

Github will tell you in your Pull Request whether all merge conflicts have been solved.

Hope this helps 🙂

Attention: DSC V2 Pull Server on 2012 R2 Core supported again

For a while now a DSC V2 Pull Server hasn’t been functioning properly on Server 2012 R2 Core. The underlying cause is that the xPSDesiredStateConfiguration resource module has been configuring the DSC Service with an OLEDB provider (Jet) which is actually deprecated starting with 2016 and doesn’t work with Server 2012 R2 Core.

The error messages you encounter if this bug has hit you look like this (see pull server event log):

Registration of Dsc Agent with AgentID = f7fb8bbc-f48a-435b-8952-e4a534f4d1b0 failed. The underlying error is : The ‘Microsoft.Jet.OLEDB.4.0’ provider is not registered on the local machine.

Since module version the db provider has been changed to ESENT again which removes the incompatibility and makes the Pull Server work on 2012 R2 Core again.

This means that all V2 Pull Servers that were set up with the xDscWebService resource on Server 2012 R2 prior to are configured with OLEDB so consider redeploying the Server entirely.

The file C:\inetpub\PSDSCPullServer\web.config should contain this section…:

    <add key="MaxConcurrentRequests" value="10000" />
    <add key="MaxRequestsPerTimeslot" value="10000" />
    <add key="TimeslotSize" value="1" />
    <add key="dbprovider" value="ESENT" />
    <add key="dbconnectionstr" value="C:\Program Files\WindowsPowerShell\DscService\Devices.edb" />
    <add key="ConfigurationPath" value="C:\Program Files\WindowsPowerShell\DscService\Configuration" />
    <add key="ModulePath" value="C:\Program Files\WindowsPowerShell\DscService\Modules" />
    <add key="RegistrationKeyPath" value="C:\Program Files\WindowsPowerShell\DscService" />

…and upon registration of a Pull server client via LCM ReportServerWeb resource the edb gets created (indicators for proper configuration in red):

PS H:\> ls "C:\Program Files\WindowsPowerShell\DscService"

    Direcory: C:\Program Files\WindowsPowerShell\DscService

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d---s-       17.03.2016     16:49                Configuration
d---s-       17.03.2016     16:49                Modules
-a----       21.03.2016     18:33        1056768 Devices.edb
-a----       13.01.2016     04:59        1310720 Devices.mdb
-a----       21.03.2016     18:33           8192 edb.chk
-a----       21.03.2016     18:33         131072 edb.log
-a----       21.03.2016     18:33         131072 edb00001.log
-a----       21.03.2016     18:33         131072 edbres00001.jrs
-a----       21.03.2016     18:33         131072 edbres00002.jrs
-a----       21.03.2016     18:33         131072 edbtmp.log
-a----       21.03.2016     17:26             39 RegistrationKeys.txt
             21.03.2016     18:33        1056768 tmp.edb

Just a heads-up: Don’t use the sample scripts from the xPSDesiredStateConfiguration module as they are outdated (time of this writing – 22.03.16). Please have a look for the official DSC documentation at for Pull Server configuration examples.





Quick Tip: Bulk remove selected vSphere snapshots

If you want to manually select the snapshots you want to delete in vSphere through PowerCLI the only viable option is piping your snapshots to the Out-GridView Cmdlet. Unfortunately it uses the default display properties of the snaps

$snaps = Get-VM | Get-Snapshot
$snaps | Out-GridView -PassThru 


As you can see the properties that are shown by default don’t help at all. So what do we do? Right, we select the properties we want to see before piping to Out-GridView:

So far so good, but what happens when we pipe the selected snapshots to Remove-Snapshot? It fails. Select-Object creates new objects and they have a different type than the snapshot objects:

The best workaround is to use the Select-FromGridView function from the technet gallery which separates the displayed object’s properties from the objects passed down the pipeline:

$delete = $snaps | Select-FromGridView -By vm,created,name,description,sizegb
$delete | Remove-Snapshot -RunAsync -Confirm:$false -Verbose

Hope this helps 🙂

Quick tip: Pre-compile assemblies to improve PS startup time

Just a quick reminder for myself.

$NGENPath = Join-Path $FrameworkDir 'ngen.exe'

[AppDomain]::CurrentDomain.GetAssemblies() |
  Select-Object -ExpandProperty Location |
  ForEach-Object { & $NGENPath """$_""" } 

Powershell loads and JIT compiles tons, and tons, and tons of stuff. This takes a lot of excessive CPU cycles and disk I/O.

ngen.exe (Native Image Generator) can be used to precompile the assemblies that Powershell loads during startup.


Best Practice – Git commit messages

Best practices:

  • The first line should contain 50 characters or less and explain the change very briefly.
  • The next paragraph should contain a bit more explanation about what you are trying to solve. Try to keep the length of the line under 72 characters; this way, it’s easy to scan people.
  • If you have more information that you want to tell, you can do so in the next paragraph. This can be as long and detailed as you want. More details are better!
  • If you want to create a list, use the – or * characters to do so.
  • When you’re working on an issue, add a reference to that issue in the last line.

An example of a nicely formatted Git commit message is as follows:

Show an error page if the user is not logged in

When a user is not logged in, we do not show an error message.
This was confusing to some users. We now show the correct error

* Here is some extra information
* And here is some more bullet information

Fixes: #1123

Using self-signed certificates for encryption

Update: Changed Hash Algorithm from SHA1 to SHA256 and pointed out that the certificate template needs to be properly encoded.

We needed a way to securely store credentials for a number of tasks in our Citrix environment so I suggested using an encryption certificate for that.

It’s quite simple:

Signature = "$Windows NT$"


Subject = "cn=CitrixPowershellEncryption"
MachineKeySet = false
KeyLength = 2048
HashAlgorithm = SHA256
Exportable = true
RequestType = Cert
ValidityPeriod = "Years"
ValidityPeriodUnits = "1000"


Save this certificate template as ASCII-encoded CitrixEncryptionTemplate.inf (it contains both the data and key encipherment key usage attributes to make it compliant with WMF5) and use it to create a certificate like this:

certreq.exe -new C:\Users\megamorf\Desktop\CitrixEncryptionTemplate.inf C:\Users\megamorf\Desktop\CitrixEncryption.cer

This will do two things:

  1. Generate the public key C:\Users\megamorf\Desktop\CitrixEncryption.cer
  2. Store the full certificate in Cert:\CurrentUser\My

I exported the full certificate with private key and all extensions as pfx and used a long and complex passphrase to protect it.

Import the pfx for the account where you need it and use it like this:

$Cert = Get-ChildItem Cert:\CurrentUser\My | Where-Object {$_.Subject -like "CN=CitrixPowershellEncryption"}

# Encryption:
$msg = "my super secret password"
$EncodedPwd = [system.text.encoding]::UTF8.GetBytes($msg)
$EncryptedBytes = $Cert.PublicKey.Key.Encrypt($EncodedPwd, $true)
$EncryptedPwd = [System.Convert]::ToBase64String($EncryptedBytes)


# Decryption:
$EncryptedBytes = [System.Convert]::FromBase64String($EncryptedPwd)
$DecryptedBytes = $Cert.PrivateKey.Decrypt($EncryptedBytes, $true)
$DecryptedPwd = [system.text.encoding]::UTF8.GetString($DecryptedBytes)

my super secret password

Using PowerShell v5

# Encryption:
# version 1 - public key is stored in the filesystem
Protect-CmsMessage -Content $msg -To .\CitrixEncryption.cer -OutFile .\EncryptedMessage.cms

# version 2 - public key from certificate store is used
Protect-CmsMessage -Content $msg -To "CN=CitrixPowershellEncryption" -OutFile .\EncryptedMessage.cms

# Decryption:
Unprotect-CmsMessage -Path .\EncryptedMessage.cms

my super secret password



WMF5 finally re-released

Microsoft have finally released the long-awaited fixed version of WMF5. I personally believe that it’s intolerable to let one’s users wait two months just for this simple fix.

Today, we have republished the Windows Management Framework (WMF) 5.0 RTM packages for Windows Server 2012 R2, Windows Server 2012, Windows 2008 R2 SP1, Windows 8.1, and Windows 7 SP1 to Microsoft Download Center. These packages replace previously released WMF 5.0 RTM packages and WMF 5.0 Production Preview packages.

Important Note: For systems running Windows 7 SP1 or Windows Server 2008 R2 SP1, you’ll need WMF 4.0 and .NET Framework 4.5 or higher installed to run WMF 5.0.

We want to extend our sincere thanks to the PowerShell Community for your continued patience and support while we were addressing thePSModulePath issue found in the previously released WMF 5.0 RTM packages.

To learn about the new features and improvements in WMF 5.0 RTM, please refer to (and contribute to) the WMF 5.0 release notes.

Please continue to provide feedback via our new UserVoice site.

Changes in the republished packages
  • The KB numbers of these packages (KB3134758, KB3134759, and KB3134760) are different than previously released WMF 5.0 RTM packages (KB3094174, KB3094175, and KB3094176).
  • These packages have fixes only for the PSModulePath issue compared to the previously released WMF 5.0 RTM packages.
Uninstall previously released WMF 5.0 RTM packages

You must uninstall previously released WMF 5.0 RTM (KB3094174, KB3094175, and KB3094176) packages.


Gitlab in Production – Part 4: Gitlab Installation

Welcome to part 4 of my Gitlab series – in this part you’ll see how Gitlab can be installed and how you do the initial configuration.

Note: this part is still work in progress so let me know if I need to be more elaborate with my explanations

Gitlab Installation

The gitlab installation manual recommends using their Omnibus packages for the setup:

We recommend installing the Omnibus package instead of installing GitLab from source. Omnibus GitLab takes just 2 minutes to install and is packaged in the popular deb and rpm formats. Compared to an installation from source, the Omnibus package is faster to install and upgrade, more reliable to upgrade and maintain, and it shortens the response time for our subscribers’ issues. A package contains GitLab and all its depencies (Ruby, PostgreSQL, Redis, Nginx, Unicorn, etc.), it can be installed without an internet connection. For troubleshooting and configuration options please see the Omnibus GitLab readme.

In addition to the Omnibus packages they provide bash scripts that take care of everything for you (chef, puppet and bundler configurations are available as well) and that’s what we’re going to use. In our case, since we’re using Debian, we run the deb installation script like this:

curl | sudo bash

What’s happening behind the scenes is this:

  • Detect OS (and its version)
  • Install required packages for the actual Gitlab installation
  • Add Gitlab Package Repositories to /etc/apt/sources.list.d/
  • Add PGP Keys for Gitlab Package Repositories

It adds the following packages…

apt-get install -q -y curl
apt-get install -y debian-archive-keyring &> /dev/null
apt-get install -y apt-transport-https

… then adds the Gitlab Repositories…

ladmin@srvapp037:~/scripts$ cat /etc/apt/sources.list.d/gitlab_gitlab-ce.list
# this file was generated by for
# the repository at

deb jessie main
deb-src jessie main

… and finally installs the GPG key:

curl 2> /dev/null | apt-key add - &>/dev/null

So now that we’ve run the script which has taken care of all the prerequisites for us we can finally install Gitlab CE:

sudo apt-get update
sudo apt-get install gitlab-ce

It’ll take a bit and you’ll see the output from their chef recipes before it shows you that all Gitlab services have been started.

Gitlab Configuration

SSL Configuration

I’ve created a SHA2 certificate for the Gitlab web server with the SAN field (Subject Alternative Name) containing the alias name of the server name that we’re going to use from now on.

common name: srvapp037.mydomain.internal
SAN: srvapp037, git, git.mydomain.internal

Next we create the certificate folder on our git server, apply proper permissions and place our certificates in there:

sudo mkdir /etc/gitlab/ssl
sudo chmod 700 /etc/gitlab/ssl
sudo vim /etc/gitlab/ssl/git.mydomain.internal.crt
sudo vim /etc/gitlab/ssl/git.mydomain.internal.key

Note: I’ve pasted the certificate contents from the clipboard, that’s why I’m using vim here

After copying my new cert to the server I have to configure our Gitlab webserver by editing the Gitlab configuration file /etc/gitlab/gitlab.rb:

external_url 'https://git.mydomain.internal&#39;
nginx['enable'] = true
nginx['client_max_body_size'] = '250m'
nginx['redirect_http_to_https'] = true
nginx['ssl_certificate'] = "/etc/gitlab/ssl/git.mydomain.internal.crt"
nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/git.mydomain.internal.key"
nginx['ssl_ciphers'] = "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"
nginx['ssl_prefer_server_ciphers'] = "on"
nginx['ssl_protocols'] = "TLSv1 TLSv1.1 TLSv1.2" # recommended by &
nginx['ssl_session_cache'] = "builtin:1000 shared:SSL:10m" # recommended in

view raw


hosted with ❤ by GitHub

The most important line here is

nginx['ssl_ciphers'] = "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"

It contains two cipher suites that make use of elliptic curve key exchanges. These are TLS 1.2 only which is fine since we don’t have to ensure backwards compatibility (IE10 doesn’t support TLS 1.2 but isn’t supported anymore anyway).

Then restart Gitlab with:

sudo gitlab-ctl reconfigure
sudo gitlab-ctl restart

So far so good, right? Nope, nginx doesn’t start – browsing to https://git.mydomain.internal gives me a server error message. So what’s the problem?

Well, it turns out that nginx doesn’t know how to deal with the passphrase on my certificate key. You can see that by looking at the nginx logs under /var/log/gitlab/nginx/:

ladmin@srvapp037:~$ sudo grep –iR "error" /var/log/gitlab/nginx/
2015/11/03 17:38:04 [emerg] 21995#0: SSL_CTX_use_PrivateKey_file("/etc/gitlab/ssl/git.mydomain.internal.key") failed (SSL: error:0906406D:PEM routines:PEM_def_callback:problems getting password error:0906A068:PEM routines:PEM_do_header:bad password read error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib)

You can remove the passphrase from the key with OpenSSL like this:

sudo openssl rsa -in /etc/gitlab/ssl/git.mydomain.internal.key -out /etc/gitlab/ssl/git.mydomain.internal.nocrypt.key

Since we now have a key file without passphrase we need to change one line in the gitlab nginx configuration as follows:

# old:
nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/git.mydomain.internal.key"
# new:
nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/git.mydomain.internal.nocrypt.key"

LDAP Authentication

Note: we’re in the process of setting up a PKI which is why we still connect via LDAP (TCP\389) instead of LDAPS (TCP\636) – the required steps for LDAPS will be added once the PKI is in place.

Again we edit our global Gitlab configuration file /etc/gitlab/gitlab.rb:

gitlab_rails['ldap_enabled'] = true
gitlab_rails['ldap_servers'] = YAML.load <<-'EOS' # remember to close this block with 'EOS' below
main: # 'main' is the GitLab 'provider ID' of this LDAP server
label: 'LDAP'
host: 'mydomain.internal'
port: 389
uid: 'sAMAccountName'
method: 'plain' # "tls" or "ssl" or "plain"
bind_dn: 'CN=ldap query,OU=ServiceAccounts,DC=mydomain,DC=internal'
password: 'ASuperSecretPassword'
active_directory: true
allow_username_or_email_login: false
block_auto_created_users: true
base: 'OU=UserAccounts,DC=mydomain,DC=internal'
user_filter: ''
## EE only
group_base: ''
admin_group: ''
sync_ssh_keys: false

view raw


hosted with ❤ by GitHub

I believe that is fairly self-explanatory but I’ll cover the settings really quick anyway. The easiest way to get the bind_dn path is to use PowerShell with the Active Directory module:

Import-Module ActiveDirectory
Get-AdUser -Identity <ldap-service-account-samaccountname>

Base – will be used as searchbase to find your LDAP users
active_directory: true – just means that the LDAP server is a Microsoft AD
allow_username_or_email_login: false – we want users to login with their samaccountname and not their email address so we disable this
block_auto_created_users: true – if an LDAP user authenticates against Gitlab for the first time it is imported into Gitlab but needs to be unblocked by a Gitlab administrator before the user can actually log in

So now that this is in place we can apply the configuration by running

sudo gitlab-ctl reconfigure

SMTP Configuration

In our case the SMTP configuration is fairly simple:

gitlab_rails['gitlab_email_reply_to'] = ''
gitlab_ci['smtp_enable'] = true
gitlab_ci['smtp_address'] = "mailrelay01.mydomain.internal"
gitlab_ci['smtp_port'] = 25
gitlab_ci['smtp_domain'] = "mydomain.internal"
gitlab_ci['smtp_authentication'] = false

view raw


hosted with ❤ by GitHub

I enable SMTP, specify our internal relay with its SMTP port and domain and don’t use authentication (Note: I know but the Gitlab server and relay are on the management network that has additional security measures in place). As you can see I set the Reply-To field to my public email address so that I can respond to accidental replies to Gitlab emails.

Verify Webserver Security

In order to verify that the webserver is secure I use the testssl shell script from Just download the archive and extract it:



What we have now is a fully usable Gitlab Server. In the next part I’m going to cover sensible user defaults and the steps to configure your git client for use with our server.

Gitlab in Production – Part 3: Server Setup

Welcome to part 3 of my Gitlab series. I’m going to cover the pitfalls I encountered while trying to get Gitlab up and running as well as the steps required to set it up yourself.

Our CIO has defined a set of rules and guidelines through a system called Enterprise Architecture Management (EAM) that dictates which software and operating systems we should use in our corporate IT environment.

EAM Compliance

We use SUSE Linux Enterprise Server (SLES) 11 SP4 as standard Linux OS. SLES is not listed as supported OS for Gitlab but I’d really like to use it in order to keep the number of different OS’ I have to support to a minimum (see EAM). The setup itself is simple, just download the Gitlab omnibus installer and run the setup, right? Nope, even though SLES uses rpm the installation with the CentOS rpm installation script fails. No biggie, then I just download the rpm and install it manually. Again, no sucsess. During the troubleshooting process I see that for Gitlab glibc version 2.14 is required but SLES 11 SP4 ships with 2.11. Oh well, it was worth a try.

But wait, this is when I found the Bitnami Gitlab installer that bundles all of the required components in an all-in-one installation. I download the setup, make it executable with chmod +x /tmp/ and run it. During the setup I answer a few configuration options and we’re done. Gitlab is up and running. Phew, easier than expected. Everything works until…. the system is rebooted. The redis service won’t start no matter what I do. Reading up on the Bitnami Gitlab installer feedback made me realize that its upgrades are error-prone and time consuming. This is where I stopped wasting my time just to be compliant with our corporate policy.

Fresh start

I had stopped trying to get Gitlab running on SLES at this point as the maintenance costs of running Gitlab on an unsupported OS would’ve been a lot more than what we have to invest in supporting an additional server OS.

I discussed the OS choices with my colleagues and we didn’t really have a preference so we went with Debian 8. 2 (Jessie). I provision a new VM in vSphere, grab a free IP address from our Microsoft IPAM (IP Address Management) service (tracking IPs in spreadsheets nowadays is a big no-no) and run the Debian setup.

Firewall rules

In order for the Debian server to reach its update servers from our internal network the following domains had to be whitelisted:

# outgoing traffic (http/https)
# the following domain is required for the Gitlab installation

Additional Packages

Since I’m more familiar with Ubuntu I was missing the sudo command on Debian. Switch to root and add our new local user (linux admin = “ladmin”) to the sudo group to grant it admin privileges and set his password (log out and back in to apply them):

apt-get install sudo
adduser ladmin sudo
passwd ladmin
install sudo as root and set up user with admin privileges

I need a few packages to be productive on the machine so let’s install the following packages:

sudo apt-get update && sudo apt-get upgrade
sudo apt-get install vim screen tree open-vm-tools unattended-upgrades apt-listchanges curl ca-certificates
Packages excluding the ones required for Gitlab

Welcome screen

If you’ve ever used Ubuntu you’ve probably come across the output of their landscape monitoring tool:ubuntu-landscape

A while ago I’ve written a shell script to output the same information on our SLES boxes. I had to change it a little to make it work on Debian. [Link to article will be added later]

I store the script as /etc/profile.d/ so that it will be displayed everytime a user logs in:

Using username "ladmin".
LEGAL NOTICE: Use of this device restricted to authorized persons. 
This device is subject to monitoring at all times, use of this device 
constitutes consent to monitoring.
Authenticating with public key "MyDomain Key - ladmin srvapp037 - megamorf" from agent
No mail.
Last login: Fri Nov 6 17:57:08 2015 from xpc001.mydomain.internal

Welcome to Debian GNU/Linux 8.2 (jessie)
 (GNU/Linux 3.16.0-4-amd64 x86_64)

 * motd made by: megamorf

 System information as of Fr 6. Nov 18:00:01 CET 2015
 You're connected from:

 System load: 0.02 Memory usage:        751/1000MB(75%)
 Processes:   127  IP address for eth0:
 User count:  1

 Disk Utilization:
 Usage of / 25% of 8,2G
 Usage of /home 1% of 26G
 Usage of /var 32% of 2,7G
 Usage of /tmp 24% of 360M

 Connected Users:
 UserLogin         timeSource
 ladmin 2015-11-05 16:13(xpc001:S.0)
 ladmin 2015-11-05 16:13(xpc001:S.1)
 ladmin 2015-11-06 17:57(xpc001.mydomain.internal)

Custom Debian Login Message

Custom gnu screen configuration

I like to run screen to be able to use multiple persistent terminals on the target machine that survive a disconnected SSH session.

Now that screen is installed it’s time to customize the global screen configuration /etc/screenrc. Add the following lines at the end of the configuration file. This will add the computername, the current time and tabs to the screen terminal.

hardstatus on
hardstatus alwayslastline
hardstatus string "%{.bW}%-w%{.rW}%n %t%{-}%+w %=%{..G} %H %{..Y} %d/%m %C%a "

view raw


hosted with ❤ by GitHub

Hardening SSH


So the first thing I do is define the order of SSH hostkeys from secure to less secure. Root Login and X11 forwarding is not permitted. Finally I define the allowed SSH ciphers,  MACs and key exchange algorithms from secure to less secure.

root@srvapp037:/tmp# egrep -v "^#|^$" /etc/ssh/sshd_config

# Supported HostKey algorithms by order of preference for protocol version 2.
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key

# Use kernel sandbox mechanisms where possible in unprivileged processes
UsePrivilegeSeparation yes

PermitRootLogin no
StrictModes yes
RSAAuthentication yes
PubkeyAuthentication yes
IgnoreRhosts yes
RhostsRSAAuthentication no
HostbasedAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
X11Forwarding no
PrintMotd no

# Corporate Security Settings:
relevant lines in /etc/ssh/sshd_config colored in red

Don’t forget to apply the new configuration with sudo service ssh restart

Enable automatic installation of security updates

Since this is the only non-SLES machine that isn’t a vendor-hardened virtual appliance in our environment we need to ensure that security updates are installed on a regular basis. But we’re lazy so we let the server take care of that. We take the vanishingly small risk of the system breaking due to a security update in exchange for the time savings from automatic update installation (the system is backed up on  a daily basis anyway)

1) uncomment two lines in /etc/apt/apt.conf.d/50unattended-upgrades to enable unattended security updates and notify you of update problems:

# --- output omitted ---
Unattended-Upgrade::Origins-Pattern {
        // Codename based matching:
        // This will follow the migration of a release through different
        // archives (e.g. from testing to stable and later oldstable).
//      "o=Debian,n=jessie";
//      "o=Debian,n=jessie-updates";
//      "o=Debian,n=jessie-proposed-updates";
//      "o=Debian,n=jessie,l=Debian-Security";

        // Archive or Suite based matching:
        // Note that this will silently match a different release after
        // migration to the specified archive (e.g. testing becomes the
        // new stable).
//      "o=Debian,a=stable";
//      "o=Debian,a=stable-updates";
//      "o=Debian,a=proposed-updates";
# --- output omitted ---
// Send email to this address for problems or packages upgrades
// If empty or unset then no email is sent, make sure that you
// have a working mail setup on your system. A package that provides
// 'mailx' must be installed. E.g. ""
Unattended-Upgrade::Mail "root";
# --- output omitted ---
Uncomment the red lines

2) run sudo dpkg-reconfigure -plow unattended-upgrades to create the apt 20auto-upgrades configuration file

3) verify the /etc/apt/apt.conf.d/20auto-upgrades file looks like this:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
Set both entries to 1 in /etc/apt/apt.conf.d/20auto-upgrades

You can then do a test run to see that everything is working as expected (Note: it’s unattended-upgrade in this case, without a trailing “s”):

sudo unattended-upgrade --debug --dry-run

Details can be found in the log file /var/log/unattended-upgrades/unattended-upgrades.log

This concludes our preparation for the actual Gitlab installation. Stay tuned for part 4 and don’t hesitate to give constructive feedback 😉