Smartcard configuration against Active Directory NoMethodError "(undefined method `dn' for nil:NilClass):" Results in a 500 error

Searched all over and cannot find anything very similar.

GitLab-ee 17.7.0 Self-Managed. We use our instance in the DoD and wish to use the smartcard capabilities with LDAP/Active Directory. Regular usernames and password work with the base specified, however, giving GitLab our smartcard cert results in an error I am not familiar with.

My LDAP configuration

 gitlab_rails['ldap_servers'] = YAML.load <<-'EOS'
   main: # 'main' is the GitLab 'provider ID' of this LDAP server
     label: 'redacted'
     uid: 'sAMAaccountName'
     host: 'redacted'
     port: 636
     encryption: 'simple_tls'
     verify_certificates: true
     ca_file: '/etc/gitlab/trusted-certs/tls-ca-bundle.pem'
     smartcard_auth: optional
     smartcard_ad_cert_field: 'userPrincipalName'
     smartcard_ad_cert_format: 'principal_name'
     timeout: 30
     active_directory: true
     allow_username_or_email_login: false
     attributes:
       username: 'sAMAccountName'
       email: 'mail'
       name: 'displayName'
     lowercase_usernames: true
     block_auto_created_users: false
     base: 'redacted'

Error Message


NoMethodError (undefined method `dn' for nil:NilClass):

ee/lib/gitlab/auth/smartcard/ldap_certificate.rb:20:in `find_user'
ee/lib/gitlab/auth/smartcard/base.rb:36:in `find_or_create_user'
ee/app/controllers/smartcard_controller.rb:66:in `sign_in_with'
ee/app/controllers/smartcard_controller.rb:26:in `verify_certificate'
actionpack (7.0.8.6) lib/action_controller/metal/basic_implicit_render.rb:6:in `send_action'
actionpack (7.0.8.6) lib/abstract_controller/base.rb:215:in `process_action'
actionpack (7.0.8.6) lib/action_controller/metal/rendering.rb:165:in `process_action'
actionpack (7.0.8.6) lib/abstract_controller/callbacks.rb:234:in `block in process_action'
activesupport (7.0.8.6) lib/active_support/callbacks.rb:118:in `block in run_callbacks'
lib/gitlab/ip_address_state.rb:11:in `with'
ee/app/controllers/ee/application_controller.rb:45:in `set_current_ip_address'
activesupport (7.0.8.6) lib/active_support/callbacks.rb:127:in `block in run_callbacks'
app/controllers/application_controller.rb:506:in `set_current_admin'
activesupport (7.0.8.6) lib/active_support/callbacks.rb:127:in `block in run_callbacks'
lib/gitlab/session.rb:11:in `with_session'
app/controllers/application_controller.rb:496:in `set_session_storage'
activesupport (7.0.8.6) lib/active_support/callbacks.rb:127:in `block in run_callbacks'
lib/gitlab/i18n.rb:114:in `with_locale'
app/controllers/application_controller.rb:489:in `set_locale'
activesupport (7.0.8.6) lib/active_support/callbacks.rb:127:in `block in run_callbacks'
app/controllers/application_controller.rb:480:in `set_current_context'
activesupport (7.0.8.6) lib/active_support/callbacks.rb:127:in `block in run_callbacks'
marginalia (1.11.1) lib/marginalia.rb:109:in `record_query_comment'
activesupport (7.0.8.6) lib/active_support/callbacks.rb:127:in `block in run_callbacks'
sentry-rails (5.21.0) lib/sentry/rails/controller_transaction.rb:32:in `block in sentry_around_action'
sentry-ruby (5.21.0) lib/sentry/hub.rb:108:in `with_child_span'
sentry-ruby (5.21.0) lib/sentry-ruby.rb:499:in `with_child_span'
sentry-rails (5.21.0) lib/sentry/rails/controller_transaction.rb:18:in `sentry_around_action'
activesupport (7.0.8.6) lib/active_support/callbacks.rb:127:in `block in run_callbacks'
activesupport (7.0.8.6) lib/active_support/callbacks.rb:138:in `run_callbacks'
actionpack (7.0.8.6) lib/abstract_controller/callbacks.rb:233:in `process_action'
actionpack (7.0.8.6) lib/action_controller/metal/rescue.rb:23:in `process_action'
actionpack (7.0.8.6) lib/action_controller/metal/instrumentation.rb:67:in `block in process_action'
activesupport (7.0.8.6) lib/active_support/notifications.rb:206:in `block in instrument'
activesupport (7.0.8.6) lib/active_support/notifications/instrumenter.rb:24:in `instrument'
activesupport (7.0.8.6) lib/active_support/notifications.rb:206:in `instrument'
actionpack (7.0.8.6) lib/action_controller/metal/instrumentation.rb:66:in `process_action'
actionpack (7.0.8.6) lib/action_controller/metal/params_wrapper.rb:259:in `process_action'
activerecord (7.0.8.6) lib/active_record/railties/controller_runtime.rb:27:in `process_action'
actionpack (7.0.8.6) lib/abstract_controller/base.rb:151:in `process'
actionview (7.0.8.6) lib/action_view/rendering.rb:39:in `process'
actionpack (7.0.8.6) lib/action_controller/metal.rb:188:in `dispatch'
actionpack (7.0.8.6) lib/action_controller/metal.rb:249:in `block in dispatch'
lib/gitlab/middleware/action_controller_static_context.rb:23:in `call'
actionpack (7.0.8.6) lib/action_controller/metal.rb:249:in `dispatch'
actionpack (7.0.8.6) lib/action_dispatch/routing/route_set.rb:49:in `dispatch'
actionpack (7.0.8.6) lib/action_dispatch/routing/route_set.rb:32:in `serve'
actionpack (7.0.8.6) lib/action_dispatch/journey/router.rb:50:in `block in serve'
actionpack (7.0.8.6) lib/action_dispatch/journey/router.rb:32:in `each'
actionpack (7.0.8.6) lib/action_dispatch/journey/router.rb:32:in `serve'
actionpack (7.0.8.6) lib/action_dispatch/routing/route_set.rb:852:in `call'
gitlab-experiment (0.9.1) lib/gitlab/experiment/middleware.rb:19:in `call'
omniauth (2.1.0) lib/omniauth/strategy.rb:202:in `call!'
omniauth (2.1.0) lib/omniauth/strategy.rb:169:in `call'
flipper (0.26.2) lib/flipper/middleware/memoizer.rb:72:in `memoized_call'
flipper (0.26.2) lib/flipper/middleware/memoizer.rb:37:in `call'
lib/gitlab/metrics/elasticsearch_rack_middleware.rb:16:in `call'
lib/gitlab/middleware/sidekiq_shard_awareness_validation.rb:20:in `block in call'
lib/gitlab/sidekiq_sharding/validator.rb:42:in `enabled'
lib/gitlab/middleware/sidekiq_shard_awareness_validation.rb:20:in `call'
lib/gitlab/middleware/memory_report.rb:13:in `call'
lib/gitlab/middleware/speedscope.rb:13:in `call'
lib/gitlab/database/load_balancing/rack_middleware.rb:23:in `call'
lib/gitlab/middleware/rails_queue_duration.rb:33:in `call'
lib/gitlab/etag_caching/middleware.rb:21:in `call'
lib/gitlab/metrics/rack_middleware.rb:16:in `block in call'
lib/gitlab/metrics/web_transaction.rb:46:in `run'
lib/gitlab/metrics/rack_middleware.rb:16:in `call'
lib/gitlab/middleware/go.rb:21:in `call'
lib/gitlab/middleware/query_analyzer.rb:11:in `block in call'
lib/gitlab/database/query_analyzer.rb:83:in `within'
lib/gitlab/middleware/query_analyzer.rb:11:in `call'
lib/ci/job_token/middleware.rb:11:in `call'
batch-loader (2.0.5) lib/batch_loader/middleware.rb:11:in `call'
rack-attack (6.7.0) lib/rack/attack.rb:103:in `call'
apollo_upload_server (2.1.6) lib/apollo_upload_server/middleware.rb:19:in `call'
lib/gitlab/middleware/multipart.rb:173:in `call'
rack-attack (6.7.0) lib/rack/attack.rb:127:in `call'
warden (1.2.9) lib/warden/manager.rb:36:in `block in call'
warden (1.2.9) lib/warden/manager.rb:34:in `catch'
warden (1.2.9) lib/warden/manager.rb:34:in `call'
rack-cors (2.0.2) lib/rack/cors.rb:102:in `call'
rack (2.2.10) lib/rack/tempfile_reaper.rb:15:in `call'
rack (2.2.10) lib/rack/etag.rb:27:in `call'
rack (2.2.10) lib/rack/conditional_get.rb:27:in `call'
rack (2.2.10) lib/rack/head.rb:12:in `call'
actionpack (7.0.8.6) lib/action_dispatch/http/permissions_policy.rb:38:in `call'
actionpack (7.0.8.6) lib/action_dispatch/http/content_security_policy.rb:36:in `call'
lib/gitlab/middleware/read_only/controller.rb:50:in `call'
lib/gitlab/middleware/read_only.rb:18:in `call'
lib/gitlab/middleware/unauthenticated_session_expiry.rb:18:in `call'
rack (2.2.10) lib/rack/session/abstract/id.rb:266:in `context'
rack (2.2.10) lib/rack/session/abstract/id.rb:260:in `call'
actionpack (7.0.8.6) lib/action_dispatch/middleware/cookies.rb:704:in `call'
lib/gitlab/middleware/same_site_cookies.rb:27:in `call'
actionpack (7.0.8.6) lib/action_dispatch/middleware/callbacks.rb:27:in `block in call'
activesupport (7.0.8.6) lib/active_support/callbacks.rb:99:in `run_callbacks'
actionpack (7.0.8.6) lib/action_dispatch/middleware/callbacks.rb:26:in `call'
sentry-rails (5.21.0) lib/sentry/rails/rescued_exception_interceptor.rb:14:in `call'
actionpack (7.0.8.6) lib/action_dispatch/middleware/debug_exceptions.rb:28:in `call'
lib/gitlab/middleware/path_traversal_check.rb:35:in `call'
lib/gitlab/middleware/handle_malformed_strings.rb:21:in `call'
sentry-ruby (5.21.0) lib/sentry/rack/capture_exceptions.rb:30:in `block (2 levels) in call'
sentry-ruby (5.21.0) lib/sentry/hub.rb:265:in `with_session_tracking'
sentry-ruby (5.21.0) lib/sentry-ruby.rb:412:in `with_session_tracking'
sentry-ruby (5.21.0) lib/sentry/rack/capture_exceptions.rb:21:in `block in call'
sentry-ruby (5.21.0) lib/sentry/hub.rb:59:in `with_scope'
sentry-ruby (5.21.0) lib/sentry-ruby.rb:392:in `with_scope'
sentry-ruby (5.21.0) lib/sentry/rack/capture_exceptions.rb:20:in `call'
actionpack (7.0.8.6) lib/action_dispatch/middleware/show_exceptions.rb:29:in `call'
lib/gitlab/middleware/basic_health_check.rb:25:in `call'
lograge (0.11.2) lib/lograge/rails_ext/rack/logger.rb:15:in `call_app'
railties (7.0.8.6) lib/rails/rack/logger.rb:25:in `block in call'
activesupport (7.0.8.6) lib/active_support/tagged_logging.rb:99:in `block in tagged'
activesupport (7.0.8.6) lib/active_support/tagged_logging.rb:37:in `tagged'
activesupport (7.0.8.6) lib/active_support/tagged_logging.rb:99:in `tagged'
railties (7.0.8.6) lib/rails/rack/logger.rb:25:in `call'
actionpack (7.0.8.6) lib/action_dispatch/middleware/remote_ip.rb:93:in `call'
lib/gitlab/middleware/handle_ip_spoof_attack_error.rb:25:in `call'
lib/gitlab/middleware/request_context.rb:15:in `call'
lib/gitlab/middleware/webhook_recursion_detection.rb:15:in `call'
request_store (1.5.1) lib/request_store/middleware.rb:19:in `call'
rack (2.2.10) lib/rack/method_override.rb:24:in `call'
rack (2.2.10) lib/rack/runtime.rb:22:in `call'
rack-timeout (0.7.0) lib/rack/timeout/core.rb:154:in `block in call'
rack-timeout (0.7.0) lib/rack/timeout/support/timeout.rb:19:in `timeout'
rack-timeout (0.7.0) lib/rack/timeout/core.rb:153:in `call'
config/initializers/fix_local_cache_middleware.rb:11:in `call'
lib/gitlab/middleware/compressed_json.rb:44:in `call'
actionpack (7.0.8.6) lib/action_dispatch/middleware/executor.rb:14:in `call'
lib/gitlab/middleware/rack_multipart_tempfile_factory.rb:19:in `call'
rack (2.2.10) lib/rack/sendfile.rb:110:in `call'
lib/gitlab/middleware/sidekiq_web_static.rb:20:in `call'
lib/gitlab/metrics/requests_rack_middleware.rb:79:in `call'
gitlab-labkit (0.37.0) lib/labkit/middleware/rack.rb:22:in `block in call'
gitlab-labkit (0.37.0) lib/labkit/context.rb:35:in `with_context'
gitlab-labkit (0.37.0) lib/labkit/middleware/rack.rb:21:in `call'
actionpack (7.0.8.6) lib/action_dispatch/middleware/request_id.rb:26:in `call'
actionpack (7.0.8.6) lib/action_dispatch/middleware/host_authorization.rb:131:in `call'
railties (7.0.8.6) lib/rails/engine.rb:530:in `call'
railties (7.0.8.6) lib/rails/railtie.rb:226:in `public_send'
railties (7.0.8.6) lib/rails/railtie.rb:226:in `method_missing'
lib/gitlab/middleware/release_env.rb:12:in `call'
rack (2.2.10) lib/rack/urlmap.rb:74:in `block in call'
rack (2.2.10) lib/rack/urlmap.rb:58:in `each'
rack (2.2.10) lib/rack/urlmap.rb:58:in `call'
puma (6.5.0) lib/puma/configuration.rb:279:in `call'
puma (6.5.0) lib/puma/request.rb:99:in `block in handle_request'
puma (6.5.0) lib/puma/thread_pool.rb:389:in `with_force_shutdown'
puma (6.5.0) lib/puma/request.rb:98:in `handle_request'
puma (6.5.0) lib/puma/server.rb:468:in `process_client'
puma (6.5.0) lib/puma/server.rb:249:in `block in run'
puma (6.5.0) lib/puma/thread_pool.rb:166:in `block in spawn_thread'

==> /var/log/gitlab/gitlab-workhorse/current <==
{"backend_id":"rails","content_type":"text/html; charset=utf-8","correlation_id":"01JGM5Z04HD1DSSG0YQ2AYP32C","duration_ms":475,"host":"redacted.mil","level":"info","method":"GET",
"msg":"access","proto":"HTTP/1.1","referrer":"https://redacted.mil/","remote_addr":"127.0.0.1:0","remote_ip":"127.0.0.1","route":"^/-/","route_id":"dash","status":500,"system":"htt
p","time":"2025-01-02T13:55:35-05:00","ttfb_ms":475,"uri":"/-/smartcard/verify_certificate?client_certificate=zMLMfj5D94GuzACBdw%252BbvZELnFkW9G0VYsINtJFJ%252B8YKER3Q%252B%252Be7zDvNvzW%252BGqs
tLOyuq5ox4%252BoOmWP5%252Ba1oF...=ldapmain","user_agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0","written_bytes":1624}

==> /var/log/gitlab/nginx/gitlab_access.log <==
10.180.42.114 - - [02/Jan/2025:13:55:35 -0500] "GET /-/smartcard/verify_certificate?client_certificate=zMLMfj5D94GuzACBdw%252BbvZELnFkW9G0VYsINtJFJ%252B8YKER3Q%252B%252Be7zDvNvzW%252BGqs
tLOyuq5ox4%252BoOmWP5%252Ba1oF...=ldapmain HTTP/2.0" 500 1624 "https://redacted.mil/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0" -

Web Page image

Details

In our enterprise we use smartcards to login to our machines, websties, and access to our buildings. We have about 50 users with our instance. I have set my instance to use smartcards in my gitlab.rb:

###! Docs: https://docs.gitlab.com/ee/administration/auth/smartcard.html
gitlab_rails['smartcard_enabled'] = true
gitlab_rails['smartcard_ca_file'] = "/etc/gitlab/trusted-certs/tls-ca-bundle.pem"
#gitlab_rails['smartcard_client_certificate_required_host'] = 'redacted.mil'
#gitlab_rails['smartcard_client_certificate_required_port'] = 3444
gitlab_rails['smartcard_required_for_git_access'] = true
gitlab_rails['smartcard_san_extensions'] = true

Manual LDAP query with rails seems to work

=> 0
irb(main):003:0> adapter = Gitlab::Auth::Ldap::Adapter.new('ldapmain') # If `main` is the L
DAP provider
=>
#<Gitlab::Auth::Ldap::Adapter:0x00007fca205e6ae0
irb(main):005:0> Gitlab::Auth::Ldap::Person.find_by_uid('redacted.user', adapter)
=>
#<Gitlab::Auth::Ldap::Person:0x00007fca21a9a900
 @entry=
  #<Net::LDAP::Entry:0x00007fca21a9cbd8
   @myhash=
    {:dn=>
      ["CN=Redacted M. User -CTR,OU=Place,OU=Place,OU=Place,OU=Place,DC=Place,DC=Place,DC=MIL"],
     :sn=>["User"],
     :givenname=>["Redacted"],
     :displayname=>["Redacted. User - CTR"],
     :memberof=>
     Lots of membership information,
     :samaccountname=>["Redacted M. User"],
     :mail=>["redacted.user@.mil"]}>,
 @provider="ldapmain">

Our smartcards are provided to us and cannot change the contents on them. I have been trying to match based on principal name because in the SAN of our certs is the PN (pricipalName) which is would DoD ID. This is how active directory finds us in the directory. I have tried using other ways to match and find the users in active directory but without success.

The SAN in my cert

image

userPrincipalName in AD

image

It's almost as if the cert is not making it to the rails backend for processing.

What is the expected correct behavior?

Smartcard users in my organization should be able to have their principal name on their cert matched to their userPriniciaplName in active directory.

Edited by midnightblue1122