Shibboleth integration

I managed to integrate Shibboleth authentication without replacing the embedded NGINX with Apache.

To do this, it’s required to compile and install this NGINX module https://github.com/nginx-shib/nginx-http-shibboleth with the correct compiling options (that can be retrieved with /opt/gitlab/embedded/sbin/nginx -V or the path where the GitLab embedded NGINX is installed).

It’s also required that Shibboleth is compiled with FastCGI support enabled.

After that these requirements are satisfied, it’s required to modify two ERB files:

/opt/gitlab/embedded/cookbooks/gitlab/templates/default/nginx.conf.erb
and
/opt/gitlab/embedded/cookbooks/gitlab/templates/default/nginx-gitlab-http.conf.erb

In the nginx.conf.erb file, the editing start after the line 10, as follow:

7 .
8 .
9 .
10 daemon off;
11
12 <% if node[‘gitlab’][‘gitlab-rails’][‘omniauth_enabled’] %>
13 <% node[‘gitlab’][‘gitlab-rails’][‘omniauth_providers’].each do|provider| %>
14 <% if provider[‘name’] == ‘shibboleth’ %>
15 load_module <%= @shibboleth_module %>;
16 <% break %>
17 <% end %>
18 <% end %>
19 <% end %>
20
21 events {
22 worker_connections <%= @worker_connections %>;
23 }
24
25 http {
26 .
27 .
28 .

In the nginx-gitlab-http.conf.erb file, the editing start after the line 153, as follow:

149 .
150 .
151 .
152 proxy_set_header <%= header[0] %> <%= header[1] %>;
153 <% end %>
154
155 <% if node[‘gitlab’][‘gitlab-rails’][‘omniauth_enabled’] %>
156 <% node[‘gitlab’][‘gitlab-rails’][‘omniauth_providers’].each do|provider| %>
157 <% if provider[‘name’] == ‘shibboleth’ %>
158
159 location = /shibauthorizer {
160 internal;
161 include /opt/gitlab/embedded/conf/fastcgi_params;
162 fastcgi_pass unix:<%= @shibboleth_authorizer_sock %>;
163 }
164
165 location /Shibboleth.sso {
166 include /opt/gitlab/embedded/conf/fastcgi_params;
167 fastcgi_pass unix:<%= @shibboleth_responder_sock %>;
168 }
169
170 location /shibboleth-sp {
171 alias /usr/share/shibboleth/;
172 }
173 location /users/auth/shibboleth {
174 proxy_pass http://<%= node[‘gitlab’][‘unicorn’][‘listen’] %>:<%= node[‘gitlab’][‘unicorn’][‘port’] %>;
175 location = /users/auth/shibboleth/callback {
176 shib_request /shibauthorizer;
177 shib_request_use_headers on;
178 proxy_pass http://<%= node[‘gitlab’][‘unicorn’][‘listen’] %>:<%= node[‘gitlab’][‘unicorn’][‘port’] %>;
179 }
180 }
181 <% break %>
182 <% end %>
183 <% end %>
184 <% end %>
185
186 location ~ (.git/gitlab-lfs/objects|.git/info/lfs/objects/batch$) {

Then in the gitlab.rb config file I added the following lines (pay attention to the HTTP_EPPN attribute. It must reflect the correct entity returned by your Identity Provider. Watch the attribute-map.xml in your shibboleth configuration):

gitlab_rails[‘omniauth_enabled’] = true
gitlab_rails[‘omniauth_allow_single_sign_on’] = true
gitlab_rails[‘omniauth_block_auto_created_users’] = false
gitlab_rails[‘omniauth_providers’] = [
{
“name” => ‘shibboleth’,
“args” => {
“shib_session_id_field” => “HTTP_SHIB_SESSION_ID”,
“shib_application_id_field” => “HTTP_SHIB_APPLICATION_ID”,
“uid_field” => “HTTP_EPPN”,
“name_field” => “HTTP_CN”,
“info_fields” => { “email” => ‘HTTP_MAIL’}
}
}
]

and

nginx[‘shibboleth_module’] = “/usr/lib64/nginx/modules/ngx_http_shibboleth_module.so”
nginx[‘shibboleth_sock’] = “/var/run/shibboleth/shibd.sock”
nginx[‘shibboleth_responder_sock’] = “/var/run/shibboleth/shibresponder.sock”
nginx[‘shibboleth_authorizer_sock’] = “/var/run/shibboleth/shibauthorizer.sock”

For the Shibboleth configuration part it’s required to add these lines:

 <UnixListener address="shibd.sock"/>

 <RequestMapper type="XML">
    <RequestMap>
       <Host name="YOUR_HOSTNAME_HERE"
          authType="shibboleth"
          requireSession="true"
          redirectToSSL="443">
          <Path name="/users/auth/shibboleth/callback" />
       </Host>
    </RequestMap>
</RequestMapper>

Now, you need to install “supervisor” and add the following lines in /etc/supervisord.d/shibboleth.ini:

[fcgi-program:shibauthorizer]
command=/usr/lib64/shibboleth/shibauthorizer
socket=unix:///var/run/shibboleth/shibauthorizer.sock
socket_owner=shibd:shibd
socket_mode=0660
user=shibd
stdout_logfile=/var/log/supervisor/shibauthorizer.log
stderr_logfile=/var/log/supervisor/shibauthorizer.error.log

[fcgi-program:shibresponder]
command=/usr/lib64/shibboleth/shibresponder
socket=unix:///var/run/shibboleth/shibresponder.sock
socket_owner=shibd:shibd
socket_mode=0660
user=shibd
stdout_logfile=/var/log/supervisor/shibresponder.log
stderr_logfile=/var/log/supervisor/shibresponder.error.log

Then start (or restart) the Shibboleth daemon (systemctl start shibd.service) and enable and start the supervisor daemon (“systemctl enable supervisor.service” and “systemctl start supervisor.service”) and start all the supervisor processes with “supervisorctl start all”.

Any suggestions or corrections are welcome!

Did this actually work as expected?
Thanks
Chris

Incredibly, with a few more tweaks, everything still works as you’d expect.

This is the content of my /opt/gitlab/embedded/cookbooks/gitlab/templates/default/nginx-gitlab-http.conf.erb # This file is managed by gitlab-ctl. Manual changes will be # erased! To change the contents below, edit /etc/gitlab/gitlab.rb # and run `sudo gitlab-ctl reconfigure`.
## GitLab
## Modified from https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/support/nginx/gitlab-ssl & https://gitlab.com/gitlab-org/gitlab-foss/blob/master/lib/support/nginx/gitlab
##
## Lines starting with two hashes (##) are comments with information.
## Lines starting with one hash (#) are configuration parameters that can be uncommented.
##
##################################
##        CHUNKED TRANSFER      ##
##################################
##
## It is a known issue that Git-over-HTTP requires chunked transfer encoding [0]
## which is not supported by Nginx < 1.3.9 [1]. As a result, pushing a large object
## with Git (i.e. a single large file) can lead to a 411 error. In theory you can get
## around this by tweaking this configuration file and either:
## - installing an old version of Nginx with the chunkin module [2] compiled in, or
## - using a newer version of Nginx.
##
## At the time of writing we do not know if either of these theoretical solutions works.
## As a workaround users can use Git over SSH to push large files.
##
## [0] https://git.kernel.org/cgit/git/git.git/tree/Documentation/technical/http-protocol.txt#n99
## [1] https://github.com/agentzh/chunkin-nginx-module#status
## [2] https://github.com/agentzh/chunkin-nginx-module
##
###################################
##         configuration         ##
###################################

upstream gitlab-workhorse {
  server <%= "unix:" if node['gitlab']['gitlab-workhorse']['listen_network'] == "unix" %><%= node['gitlab']['gitlab-workhorse']['listen_addr'] %>;
}

<% if @https && @redirect_http_to_https %>
## Redirects all HTTP traffic to the HTTPS host
server {
<% @listen_addresses.each do |listen_address| %>
  listen <%= listen_address %>:<%= @redirect_http_to_https_port %>;
<% end %>

  server_name <%= @fqdn %>;
  server_tokens off; ## Don't show the nginx version number, a security best practice

  <% if @letsencrypt_enable %>
  location /.well-known {
    root <%= @dir %>/www/;
  }
  <% end %>

  location / {
    return 301 https://<%= @fqdn %>:<%= @port %>$request_uri;
  }

  # health checks configuration
  include <%= @gitlab_health_conf %>;

  access_log  <%= @log_directory %>/gitlab_access.log gitlab_access;
  error_log   <%= @log_directory %>/gitlab_error.log;
}
<% end %>

server {
<% @listen_addresses.each do |listen_address| %>
  listen <%= listen_address %>:<%= @listen_port %><% if @https %> ssl<% if @http2_enabled %> http2<% end %><% end %>;

  <% if @kerberos_enabled && @kerberos_use_dedicated_port %>
  listen <%= listen_address %>:<%= @kerberos_port %><% if @kerberos_https %> ssl<% end %>;
  <% end %>

<% end %>
  server_name <%= @fqdn %>;
  server_tokens off; ## Don't show the nginx version number, a security best practice

  ## Increase this if you want to upload large attachments
  ## Or if you want to accept large git objects over http
  client_max_body_size <%= @client_max_body_size %>;

  <% if @https %>
  ## Strong SSL Security
  ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/
  ssl_certificate <%= @ssl_certificate %>;
  ssl_certificate_key <%= @ssl_certificate_key %>;
  <% if @ssl_client_certificate %>
  ssl_client_certificate <%= @ssl_client_certificate%>;
  <% end %>
  <% if @ssl_verify_client %>
  ssl_verify_client <%= @ssl_verify_client%>;
  ssl_verify_depth <%= @ssl_verify_depth%>;
  <% end %>

  # GitLab needs backwards compatible ciphers to retain compatibility with Java IDEs
  ssl_ciphers '<%= @ssl_ciphers %>';
  ssl_protocols  <%= @ssl_protocols %>;
  ssl_prefer_server_ciphers <%= @ssl_prefer_server_ciphers %>;
  ssl_session_cache  <%= @ssl_session_cache %>;
  ssl_session_timeout  <%= @ssl_session_timeout %>;

  <% if @ssl_dhparam %>
  ssl_dhparam <%= @ssl_dhparam %>;
  <% end %>
  <% end %>

  ## Real IP Module Config
  ## http://nginx.org/en/docs/http/ngx_http_realip_module.html
  <% if @real_ip_header %>
  real_ip_header <%= @real_ip_header %>;
  <% end %>
  <% if @real_ip_recursive %>
  real_ip_recursive <%= @real_ip_recursive %>;
  <% end %>
  <% @real_ip_trusted_addresses.each do |trusted_address| %>
  set_real_ip_from <%= trusted_address %>;
  <% end %>

  ## HSTS Config
  ## https://www.nginx.com/blog/http-strict-transport-security-hsts-and-nginx/
  <% unless @hsts_max_age.nil? || @hsts_max_age <= 0 %>
  add_header Strict-Transport-Security "max-age=<%= @hsts_max_age -%>
<% if @hsts_include_subdomains %>; includeSubdomains<% end %>";
  <% end %>

  <% if @referrer_policy %>
  # Rails sets a default policy of strict-origin-when-cross-origin, so
  # hide that and just send the one we've configured for nginx
  proxy_hide_header Referrer-Policy;
  add_header Referrer-Policy <%= @referrer_policy %>;
  <% end %>

  ## Individual nginx logs for this GitLab vhost
  access_log  <%= @log_directory %>/gitlab_access.log gitlab_access;
  error_log   <%= @log_directory %>/gitlab_error.log;

  if ($http_host = "") {
    <% default_host = Nginx.generate_host_header(@fqdn, @port, @https) %>
    set $http_host_with_default "<%= default_host %>";
  }

  if ($http_host != "") {
    set $http_host_with_default $http_host;
  }

<% if @gzip_enabled %>
  gzip <%= @gzip %>;
  gzip_static on;
  gzip_comp_level <%= @gzip_comp_level %>;
  gzip_http_version <%= @gzip_http_version %>;
  gzip_vary on;
  gzip_disable "msie6";
  gzip_min_length 250;
  gzip_proxied <%= @gzip_proxied %>;
  gzip_types <%= @gzip_types.join(' ') %>;
<% end %>

  ## https://github.com/gitlabhq/gitlabhq/issues/694
  ## Some requests take more than 30 seconds.
  proxy_read_timeout      <%= @proxy_read_timeout %>;
  proxy_connect_timeout   <%= @proxy_connect_timeout %>;
  proxy_redirect          off;
  <% if @custom_error_pages %>
  proxy_intercept_errors on;
  <% end %>
  proxy_http_version 1.1;

  <% @proxy_set_headers.each do |header| %>
  <% next if header[1].nil? || header[1].empty? %>
  proxy_set_header <%= header[0] %> <%= header[1] %>;
  <% end %>

  <% if node['gitlab']['gitlab-rails']['omniauth_enabled'] %>
  <% node['gitlab']['gitlab-rails']['omniauth_providers'].each do|provider| %>
    <% if provider['name'] == 'shibboleth' %>

  location = /shibauthorizer {
    internal;
    include /opt/gitlab/embedded/conf/fastcgi_params;
    fastcgi_pass unix:<%= @shibboleth_authorizer_sock %>;
  }

  location /Shibboleth.sso {
    include /opt/gitlab/embedded/conf/fastcgi_params;
    fastcgi_pass unix:<%= @shibboleth_responder_sock %>;
  }

  location /shibboleth-sp {
    alias /usr/share/shibboleth/;
  }
  location /users/auth/shibboleth  {
    proxy_pass http://<%= node['gitlab']['unicorn']['listen'] %>:<%= node['gitlab']['unicorn']['port'] %>;
    location = /users/auth/shibboleth/callback {
      shib_request /shibauthorizer;
      shib_request_use_headers on;
      proxy_pass http://<%= node['gitlab']['unicorn']['listen'] %>:<%= node['gitlab']['unicorn']['port'] %>;
    }
  }
        <% break %>
      <% end %>
    <% end %>
  <% end %>

  location ~ (<%= @request_buffering_off_path_regex %>) {
    proxy_cache off;
    proxy_pass http://gitlab-workhorse;
    proxy_request_buffering off;
  }

  <% if node['monitoring']['grafana']['enable'] -%>
  location <%= File.join(@path, '-/grafana/') %> {
    proxy_pass http://<%= node['monitoring']['grafana']['http_addr'] %>:<%= node['monitoring']['grafana']['http_port'] %>/;
  }

  <% end -%>
  <% if node['gitlab-kas']['enable'] -%>
  location <%= File.join(@path, '-/kubernetes-agent/') %> {
    proxy_pass http://<%= node['gitlab-kas']['listen_address'] %>/;
  }

  <% end -%>

  # health checks configuration
  include <%= @gitlab_health_conf %>;

  location <%= @path %> {
    proxy_cache off;
    proxy_pass  http://gitlab-workhorse;
  }

  location <%= File.join(@path, 'assets') %> {
    proxy_cache <%= @proxy_cache %>;
    proxy_pass  http://gitlab-workhorse;
  }

  <% errors = Nginx.parse_error_pages %>
  <% errors.each do |err, location| %>
  error_page <%= err %> /<%= location %>;
  <% end %>
  location ~ ^/(<%= errors.keys.join('|') %>)(-custom)?\.html$ {
    root <%= GitlabRails.public_path %>;
    internal;
  }

  <%= @custom_gitlab_server_config %>
}
And this is the content of my /opt/gitlab/embedded/cookbooks/gitlab/templates/default/nginx.conf.erb
# This file is managed by gitlab-ctl. Manual changes will be
# erased! To change the contents below, edit /etc/gitlab/gitlab.rb
# and run `sudo gitlab-ctl reconfigure`.

user <%= node['gitlab']['web-server']['username'] %> <%= node['gitlab']['web-server']['group']%>;
worker_processes <%= @worker_processes %>;
error_log stderr;
pid nginx.pid;

daemon off;

<% if node['gitlab']['gitlab-rails']['omniauth_enabled'] %>
  <% node['gitlab']['gitlab-rails']['omniauth_providers'].each do|provider| %>
    <% if provider['name'] == 'shibboleth' %>
load_module <%= @shibboleth_module %>;
      <% break %>
    <% end %>
  <% end %>
<% end %>

events {
  worker_connections <%= @worker_connections %>;
}

http {
  log_format gitlab_access '<%= @gitlab_access_log_format %>';
  log_format gitlab_mattermost_access '<%= @gitlab_mattermost_access_log_format %>';

  server_names_hash_bucket_size <%= @server_names_hash_bucket_size %>;

  sendfile <%= @sendfile %>;
  tcp_nopush <%= @tcp_nopush %>;
  tcp_nodelay <%= @tcp_nodelay %>;

  keepalive_timeout <%= @keepalive_timeout %>;

  gzip <%= @gzip %>;
  gzip_http_version <%= @gzip_http_version %>;
  gzip_comp_level <%= @gzip_comp_level %>;
  gzip_proxied <%= @gzip_proxied %>;
  gzip_types <%= @gzip_types.join(' ') %>;

  include /opt/gitlab/embedded/conf/mime.types;

  proxy_cache_path <%= @proxy_cache_path %>;
  proxy_cache <%= @proxy_cache %>;

  map $http_upgrade $connection_upgrade {
      default upgrade;
      ''      close;
  }

  # Remove private_token from the request URI
  # In:  /foo?private_token=unfiltered&authenticity_token=unfiltered&rss_token=unfiltered&...
  # Out: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
  map $request_uri $temp_request_uri_1 {
    default $request_uri;
    ~(?i)^(?<start>.*)(?<temp>[\?&]private[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
  }

  # Remove authenticity_token from the request URI
  # In:  /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
  # Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
  map $temp_request_uri_1 $temp_request_uri_2 {
    default $temp_request_uri_1;
    ~(?i)^(?<start>.*)(?<temp>[\?&]authenticity[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
  }

  # Remove rss_token from the request URI
  # In:  /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
  # Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=[FILTERED]&...
  map $temp_request_uri_2 $filtered_request_uri {
    default $temp_request_uri_2;
    ~(?i)^(?<start>.*)(?<temp>[\?&]rss[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
  }

  # A version of the referer without the query string
  map $http_referer $filtered_http_referer {
    default $http_referer;
    ~^(?<temp>.*)\? $temp;
  }

  <% if @status['vts_enable'] -%>
  # Enable vts status module.
  vhost_traffic_status_zone;
  <% end -%>

#  upstream gitlab-workhorse {
#    server <%= "unix:" if node['gitlab']['gitlab-workhorse']['listen_network'] == "unix" %><%= node['gitlab']['gitlab-workhorse']['listen_addr'] %>;
#  }

  <% if @gitlab_http_config %>
  include <%= @gitlab_http_config %>;
  <% end %>

  <% if @gitlab_smartcard_http_config %>
  include <%= @gitlab_smartcard_http_config %>;
  <% end %>

  <% if @gitlab_pages_http_config %>
  include <%= @gitlab_pages_http_config %>;
  <% end %>

  <% if @gitlab_mattermost_http_config %>
  include <%= @gitlab_mattermost_http_config %>;
  <% end %>

  <% if @gitlab_registry_http_config %>
  include <%= @gitlab_registry_http_config %>;
  <% end %>

  <% if @nginx_status_config %>
  include <%= @nginx_status_config %>;
  <% end %>

  <%= @custom_nginx_config %>
}

Sorry for resurrecting this old thread, but is this still the state-of-the-art method to integrate Shibboleth?