Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authorization Windows Active directory LDAP #4417

Closed
icoolchn opened this issue May 16, 2019 · 18 comments
Closed

Authorization Windows Active directory LDAP #4417

icoolchn opened this issue May 16, 2019 · 18 comments

Comments

@icoolchn
Copy link

icoolchn commented May 16, 2019

Issue Summary:

Currently using fiat-local.yml Windows Active directory LDAP for authorization.
reference https://www.spinnaker.io/setup/security/authorization/
Case use userDnPattern obtain complete user DN.But search to only one OU.
I'm using Active Directory, so I gess that the userDNpattern pattern should look similar to this one:
CN={0],OU=Users,DC=domain,DC=com
Using this pattern all users must be under OU=Users,DC=domain,DC=com ? In my case, users are under several different OU's inside the directory. Could that be an issue?
Our scenario is there are multiple users in different OU, So need to use userSearchFilter way.But found no effect.Please help us to solve, thanks.

Consider the following sample ldap database:
### user list
CN=user-test-1,OU=OU-1,OU=test,DC=test,DC=com
CN=user-test-2,OU=OU-2,OU=test,DC=test,DC=com

### group list
CN=group-test-1,OU=Group,OU=test,DC=test,DC=com
member: CN=user-test-1,OU=OU-1,OU=test,DC=test,DC=com
member: ...
##
CN=group-test-2,OU=Group,OU=test,DC=test,DC=com
member: CN=user-test-2,OU=OU-2,OU=test,DC=test,DC=com
member: ...

~/.hal/$DEPLOYMENT/profiles/fiat-local.yml

auth:
  groupMembership:
    service: LDAP
    ldap:
      roleProviderType: LDAP
      url: ldap://10.1.2.3:389/DC=test,DC=com
      managerDn: CN=ldap_manager_test,OU=ldap,OU=user,DC=test,DC=com
      managerPassword: "passwd"
      userSearchFilter: (sAMAccountName={0})
      groupSearchBase: OU=Group,OU=test
      groupSearchFilter: (member={0})
      groupRoleAttributes: cn

Cloud Provider(s):

Kubernetes 1.12.5
Spinnaker 1.13.6
OS Centos 7.4

Environment:

Spinnaker Deploy for Kubernetes .Use the halyard for deploy.

@ryanwoodsmall
Copy link

Having just gone through this, and dug into the Fiat code as a result, you should be able to get by with a groupSearchBase of OU=test as long as all of your groups are somewhere under that OU, including in deeper/nested OUs. The group search should have a scope of subtree. You can tighten up the groupSearchFilter and userSearchFilter as well to only return groups/users per respective filter.

groupSearchBase: OU=test
groupSearchFilter: (&(objectClass=group)(member={0}))
groupRoleAttributes: CN
userSearchFilter: (&(objectClass=user)(sAMAccountName={0}))

If the OUs you want to search are at the same level - i.e., OU=groups1,DC=domain,DC=com and OU=groups2,DC=domain,DC=com - you're out of luck for the current LDAP setup regardless of which one of Active Directory, OpenLDAP, etc., is being used. The groupSearchBase also must be set or Fiat will simply auth the user without any external roles and without any DEBUG-level logging:

https://github.com/spinnaker/fiat/blob/master/fiat-ldap/src/main/java/com/netflix/spinnaker/fiat/roles/ldap/LdapUserRolesProvider.java#L54

Found this the hard way yesterday.

I believe the Fiat flow (and Spring LDAP flow, as it were) is: access Deck -> user login -> Gate -> AD auth -> find roles in Fiat -> find full AD DN based on login/user name (sAMAccountName for AD) -> find groups with member attribute containing that DN -> return CN of AD groups as authorized external user roles

userDnPattern should remain unset - AD groups store user DNs in the memberOf attribute; finding DNs from sAMAccountNames is easily doable but not with a simple, single-level pattern. The DN contains the the CN, and that can't really be constructed without sub searches. userSearchFilter takes precedence if there's no userDnPattern set.

userSearchBase can also remain unset, as a subtree search will be performed against the root of the AD DIT. This can be locked down to a specific subtree if needed.

From hal config security authz ldap edit --help:

--user-search-base
    The part of the directory tree under which user searches should be performed. If
    --user-search-base isn't supplied, the search will be performed from the
    root.

--user-dn-pattern
    The pattern for finding a user's DN using simple pattern matching. For example,
    if your LDAP server has the URL ldap://mysite.com/dc=spinnaker,dc=org, and you
    have the pattern 'uid={0},ou=members', 'me' will map to a DN
    uid=me,ou=members,dc=spinnaker,dc=org. If no match is found, will try to find
    the user using --user-search-filter, if set.

Hope this helps. It was kind of a bear to figure out as all the authn/authz docs are for OpenLDAP or other standards-based LDAP servers and not the AD group layout. Definitely recommend using LDAPS (TCP port 636) as well for wire encryption, and if you have a "big" AD forest, the Global Catalog may work for searching (LDAP on 3268, LDAPS on 3269) across the entire forest.

@icoolchn
Copy link
Author

@ryanwoodsmall
thank you for your help,The configuration has been successfully used

@spinnakerbot
Copy link

This issue hasn't been updated in 45 days, so we are tagging it as 'stale'. If you want to remove this label, comment:

@spinnakerbot remove-label stale

@spinnakerbot
Copy link

This issue is tagged as 'stale' and hasn't been updated in 45 days, so we are tagging it as 'to-be-closed'. It will be closed in 45 days unless updates are made. If you want to remove this label, comment:

@spinnakerbot remove-label to-be-closed

@jessesanford
Copy link

I had a very similar problem. Using userSearchBase and userDNPattern were overkill for my needs as well and causing fiat to fail to return any groups. Also using user and group search filters with the object class queries works much better than the examples given in the documentation. I suspect as @ryanwoodsmall mentioned that the examples that are in the docs are using openldap or something other than AD as their directory backend.

@SuleimanWA
Copy link

@ryanwoodsmall what about authn? how you configured it ?

Can you provide me with the configuration for both authn and authz

@ryanwoodsmall
Copy link

~/.hal/config security snippet is something like:

...
  security:
    authn:
      ldap:
        enabled: true
        url: ldaps://ad.host.domain.name:636/DC=domain,DC=name
        userSearchFilter: (&(objectClass=user)(sAMAccountName={0}))
        managerDn: service.account@domain.name
        managerPassword: <whatever>
      enabled: true
    authz:
      groupMembership:
        service: LDAP
        ldap:
          roleProviderType: LDAP
          url: ldaps://ad.host.domain.name:636/DC=domain,DC=name
          managerDn: service.account@domain.name
          managerPassword: <whatever>
          groupSearchBase: OU=groups
          userSearchFilter: (&(objectClass=user)(sAMAccountName={0}))
          groupSearchFilter: (&(objectClass=group)(member={0}))
          groupRoleAttributes: CN
      enabled: true

This uses userPrincipalName for the manager DN, which should work for AD; the more LDAP-y way is to us the full DN, which would be something like CN=My Service Account,OU=service_accounts,DC=domain,DC=name. DC, OU, etc. will have to be changed for your particularly AD environment.

@jessesanford
Copy link

jessesanford commented Sep 10, 2019

For those with AD based ldap struggling with AUTHZ and port 636 try putting the DC=domain,DC=name at the end of the ldap url and removing it from the groupSearchBase. By putting it into the url you are telling the AD controller you are connecting to to send your requests through to the domain controller that holds the domain you are interested in. For some reason specifying the domain in the groupSearchBase does not have the same effect of routing your queries to the right place. Otherwise querying the GC on 3269 should work as well.

@ryanwoodsmall
Copy link

The search base (DC=domain,DC=name) must be specified in the LDAP URL since it's used in Fiat's DN parsing. From the Fiat code:

https://github.com/spinnaker/fiat/blob/fba4fea875699157fb6bbfa90ab7384c1eb461cb/fiat-ldap/src/main/java/com/netflix/spinnaker/fiat/roles/ldap/LdapUserRolesProvider.java#L109

And Spring (security/ldap):

https://docs.spring.io/autorepo/docs/spring-security/3.1.x/apidocs/org/springframework/security/ldap/LdapUtils.html#parseRootDnFromUrl(java.lang.String)

If the search base is removed from the LDAP URL setting, the user DN probably can't be looked up (or parsed), and no roles will be set for the user. Additionally, the groupSearchBase must be set, or the LDAP role lookup bails silently; to even get to that point, there needs to be a valid DN for the user. Sort of a catch-22 but AFAIK that's the Spring security LDAP design.

Gate works similarly, but might be a bit more forgiving if a search base is not passed in the URL.

https://github.com/spinnaker/gate/blob/master/gate-ldap/src/main/groovy/com/netflix/spinnaker/gate/security/ldap/LdapSsoConfig.groovy

@SuleimanWA
Copy link

SuleimanWA commented Sep 17, 2019

One more question regarding authorization: hot can we apply RBAC? or assign roles to group? so we can give specific permissions to specific users

@ryanwoodsmall
Copy link

Combination of AD groups (per pipeline) and k8s RBAC for provider accounts. Can't help much beyond that as it is environment-specific at that point.

@ahmei0
Copy link

ahmei0 commented Oct 1, 2020

@jessesanford @ryanwoodsmall

Need some help with LDAP based authn & authz - I have setup following config , no errors in logs but it shows just blank page after I input login details.
What I'm doing wrong here?
Other thing I'm confused is that what part of ldap config would go under gate, deck and fiat profile config etc ?
spinnakerConfig:

spec.spinnakerConfig.config - This section contains the contents of a deployment found in a halconfig .deploymentConfigurations[0]

config:
  version: 1.22.1   # the version of Spinnaker to be deployed
  security:
    apiSecurity:
      overrideBaseUrl: http://xxxx.xx
    uiSecurity:
      overrideBaseUrl: http://xxxxx.xx
    authn:
      ldap:
        enabled: true
        url: ldaps://xxxxxx/DC=xxxx,DC=local
        userSearchBase: OU=xxxxx
        userSearchFilter: (sAMAccountName={0})
        managerDn: "xxxxxx"
        managerPassword: xxxxxx
        #groupSearchBase: CN=group,OU=my,OU=groups
        #groupSearchFilter: (&(uniqueMember={0})(ObjectClass=groupOfUniqueNames))
        #groupRoleAttribute: cn
    authz:
      groupMembership:
        service: LDAP
        ldap:
          roleProviderType: LDAP
          url: ldaps://xxxxx/DC=xxxx,DC=local
          managerDn: "xxxxxx"
          managerPassword: xxxxx
          groupSearchBase: OU=Groups,OU=IT
          userSearchFilter: (sAMAccountName={0})
          groupSearchFilter: (&(objectClass=group)(member={0}))
          groupRoleAttributes: CN
      enabled: true

@ryanwoodsmall
Copy link

ryanwoodsmall commented Oct 1, 2020

First things first, get authentication working before turning on Fiat authorization. Leave authn enabled, disable authz temporarily and get LDAP-based login working, then start tinkering with Fiat.

Tighten up your userSearchFilter and specify ports in the url. Can't comment on your groupSearchBase but it may need to be widened to look at the top of the tree. managerDn for AD can use either the sAMAccountName@domain.tld for simple forests, or the full DN - regardless, I'd recommend taking the quotes out, I believe it's passed as a literal.

I use something like this:

  security:
    authn:
      ldap:
        enabled: true
        url: ldaps://adserver.domain.tld:636/DC=domain,DC=tld
        userSearchFilter: (&(objectClass=user)(sAMAccountName={0}))
        managerDn: service_account@domain.tld
        managerPassword: blahblahblah
      enabled: true
    authz:
      groupMembership:
        service: LDAP
        ldap:
          roleProviderType: LDAP
          url: ldaps://adserver.domain.tld:636/DC=domain,DC=tld
          managerDn: service_account@domain.tld
          managerPassword: blahblahblah
          groupSearchBase: OU=groups
          userSearchFilter: (&(objectClass=user)(sAMAccountName={0}))
          groupSearchFilter: (&(objectClass=group)(member={0}))
          groupRoleAttributes: CN
      enabled: true

Spinnaker docs explain reasoning on group setup: https://spinnaker.io/setup/security/authentication/ldap/#active-directory

You shouldn't have to make any changes to Deck, Gate, or Fiat - they'll be picked up on a hal deploy apply but I'd recommend adding an "admin" role using a group so you don't lock yourself out until there's another deployment, can perform global admin actions, etc. Fiat config looks something like:

~/.hal$ cat default/profiles/fiat-local.yml 
fiat:
  admin:
    roles:
      - devops

You may very well need to trust certs from AD as well; easiest way to do this is to replace the JDK cert store with a k8s secrets volume mount, i.e.:

~/.hal$ cat default/service-settings/fiat.yml 
kubernetes:
  volumes:
  - id: custom-trust-store
    mountPath: /etc/ssl/certs/java
    type: secret

Additionally, if you have a very AD large forest, you might want to use the global catalog ports. This may impact performance since a sub search will basically crawl your AD, and the admins may not like it, but it might be necessary for big directories.

@ahmei0
Copy link

ahmei0 commented Oct 2, 2020

Thanks @ryanwoodsmall for a prompt response. I already did secret mount exactly same way as you mentioned and I believe authentication is working - but problem is that after I input login details it shows me a blank page. I'll compare you config with mine and let you know. On that admin role thingy in fiat-local - is that 'devops' in this case an AD role ?

@ryanwoodsmall
Copy link

Yep, it's AD group that should map to Fiat roles when authz is working. Fiat logging isn't great - if a user's roles can't be figured from LDAP, or your search base(s) are wrong, it'll basically just crap out. I use stern (https://github.com/wercker/stern) to get k8s logs from the entire Spinnaker instance namespace, you may be able to catch Fiat stuff in there. If you have any roles defined in Front50 service accounts, they can also conflict, but that seems unlikely. Good luck!

@ahmei0
Copy link

ahmei0 commented Oct 2, 2020

Looks like my issue is due to certificate DN mismatch. And I can't find a way tell java client to ignore ldap cacert. Is there a way to add /etc/hosts entry into spinnaker pods ?

@ahmei0
Copy link

ahmei0 commented Oct 2, 2020

no worries. found a way to do this

apiVersion: spinnaker.io/v1alpha2
kind: SpinnakerService
metadata:
  name: spinnaker
spec:
  # spec.spinnakerConfig - This section is how to specify configuration spinnaker
  kustomize:
    fiat:
      deployment:
        patchesStrategicMerge:
          - |
            spec:
              template:
                spec:
                  hostAliases:

@ahmei0
Copy link

ahmei0 commented Oct 2, 2020

Thanks @ryanwoodsmall for your help. I'm able to login. Even though I don't have that fiat admin role configured, I still login as admin. Why?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants