8 Replies Latest reply on Feb 13, 2019 11:02 AM by mchoma

    Kerberos end-to-end authentication

    bklotz

      We would like to have client kerberos principal (coming from active directory) authenticate through the complete call chain which consists

       

      client application (http, SPNEGO) ->webservice(frontend jboss instance)->ejb (backend jboss)->(optional other jboss backends)->sqlserver (DB)

       

      we were able to configure the frontend jboss (SPNEGO) which can pass the call to first backend successfully, using the identityForwarding:

         static <T> T forwardIdentity(final PrivilegedExceptionAction<T> action) throws PrivilegedActionException {

              final AuthenticationConfiguration configuration = AuthenticationConfiguration.empty()

      .useForwardedIdentity(SecurityDomain.getCurrent())

      .setSaslMechanismSelector(SaslMechanismSelector.ALL);

              return AuthenticationContext.empty()

      .with(MatchRule.ALL, configuration)

      .run(action);

          }

      and the Kerberos factory (we use it in both role as acceptor server=true and initiator):

      <kerberos-security-factory name="krbSF" principal="HTTP/fronend@REALM" path="frontend.keytab" obtain-kerberos-ticket="true" request-lifetime="3600" fail-cache="1" debug="true" mechanism-names="KRB5 SPNEGO" mechanism-oids="1.3.6.1.5.5.2 1.2.840.113554.1.2.2"/>

       

      Now the questions:

      1. why going deeper in the call chain does not work (from backend to another backend)? maybe due the internal used sun.security.jgss.krb5.Krb5ProxyCredential can not be forwarded again and therefore the

      SecurityDomain.getCurrent().getCurrentSecurityIdentity()->getPrivateCredentials()

      returns 1 in frontend but 0 on backend?

       

      2. the delegation works only for 10 hours, this is the TGT ticket lifetime of the frontend server, to make this working I patched the elytron GSSCredentialSecurityFactory

       

      log.tracef("Creating GSSName for Principal '%s'", principal);

      GSSName name = manager.createName(principal.getName(), GSSName.NT_USER_NAME, KERBEROS_V5);

      int type = isServer ? ( obtainKerberosTicket ? GSSCredential.INITIATE_AND_ACCEPT : GSSCredential.ACCEPT_ONLY) : GSSCredential.INITIATE_ONLY;

      if (wrapGssCredential) {

      return new GSSKerberosCredential(wrapCredential(manager.createCredential(name, requestLifetime, mechanismOids.toArray(new Oid[mechanismOids.size()]),

      type)), kerberosTicket);

      }

      return new GSSKerberosCredential(manager.createCredential(name, requestLifetime, mechanismOids.toArray(new Oid[mechanismOids.size()]),

      type), kerberosTicket);

       

      3. tried this DS config (also on the frontend), but does not work:

      <datasource jndi-name="java:/comp/env/jdbc/MyDS" pool-name="MyDs" statistics-enabled="true">

      <connection-url> jdbc:sqlserver://serverhost:1433;DatabaseName=MyDb;EnableBulkLoad=true;BulkLoadBatchSize=500;integratedSecurity=true;authenticationScheme=JavaKerberos;serverSpn=MSSQLSvc/serverhost:1433@MyREALM</connection-url>

      <driver>sqlserver</driver>

      <pool>

      <max-pool-size>200</max-pool-size>

      <allow-multiple-users>true</allow-multiple-users>

      </pool>

      <security>

      <elytron-enabled>true</elytron-enabled>

      </security>

      <validation>

      <valid-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.mssql.MSSQLValidConnectionChecker"/>

      <validate-on-match>true</validate-on-match>

      <background-validation>false</background-validation>

      <exception-sorter class-name="org.jboss.jca.adapters.jdbc.extensions.mssql.MSSQLExceptionSorter"/>

      </validation>

      </datasource>

           it does not get any authentication, but when define

            <authentication-context>dsAuthContext</authentication-context>

          with an auth config using a kerberos factory for the database service user, it does not work as expected, because it does not use the original user principal

          but the service account from the keytab

       

      Currently we tested the above on EAP7.1.4 and 7.2 beta.

       

      Has someone experience with such working configuration?

        • 1. Re: Kerberos end-to-end authentication
          mchoma

          Exactly which step does not work? Between backend and backend?

          client application (http, SPNEGO) ->webservice(frontend jboss instance)->ejb (backend jboss)  _here propagation does not work_ (optional other jboss backends)

           

          And it is EJB to EJB call? local or remoting?

           

          Do you have default-authentication-context specified as described in [JBEAP-6687] Elytron GSSCredential propagation - JBoss Issue Tracker

           

          This is how I call EJB with GSSCredential:

           

                  AuthenticationConfiguration configuration = AuthenticationConfiguration.EMPTY
                          .useProvidersFromClassLoader(KerberosEjbPropagateIdentityTestCase.class.getClassLoader())
                          .useGSSCredential(getGSSCredential(lc.getSubject()));
                  AuthenticationContext context = AuthenticationContext.empty().with(MatchRule.ALL, configuration);

           

                  final String whoAmI = context.run(new PrivilegedExceptionAction<String>() {
                      @Override
                      public String run() throws Exception {
                          final WhoAmIBeanRemote krbBean = (WhoAmIBeanRemote) krbCtx.lookup(finalBeanLookupName);
                          return krbBean.whoAmI();
                      }
                  });
          • 2. Re: Kerberos end-to-end authentication
            bklotz

            yes, it does not work between backends, with EJB remoting (SASL), but the same call works between frontend and backend

            (at least for a while the TGT expires, the kerberos factory is in acceptor role and initiator role it caches the TGT forever, but this is bad, as it will expire, so I guess it is not prepared to handle both role in one factory->bug?)

             

             

            I could not figure out yet how to define one acceptor and one initiator with the same keytab and that the forwarding works.

            I also tried the proposed default-authentication-context, but it does not help.

            Where can I find the complete example you mentioned?

             

             

            Thank you!

            • 3. Re: Kerberos end-to-end authentication
              mchoma

              Could you provide any log  from that failing EJB1 to EJB2 remote call?

              If AuthenticationConfiguration.run() works against EJB1 I would expect same working also against EJB2. Is your security configured same on EJB2 as on EJB1. If it does not that would mean identity is not complete on EJB1 (no GSSCredentials) ? Does it mean forwarding stops working on second hop?

               

              I still dont get you requirement to be able to cache ticket forever on server side. As I understand it is application user ticket which is propagated. In that case I consider it fine it invalidates after some time. This is how Kerberos works. This period could be prolonged on client side.

              • 4. Re: Kerberos end-to-end authentication
                simkam

                Hi,

                 

                I didn't try kerberos authentication propagation using SPNEGO from browser to database with Elytron, but I was able to make it work with legacy security and some databases. There were a lot of issues with jdbc drivers. I'm sorry I don't remember much details, I played with it with WildFly 8/9. Even if you succeed with authentication to database, I wouldn't recommend it without a lot of testing. There is design issue in JCA layer which might cause connection leaks.

                 

                Ironjacamar (JCA implementation) has two level pools. Pool and ManagedConnectionPool, MCPs are stored inside map in Pool and keyed by Subject. For each new Subject (KerberosTicket/GssCredentil) new MCP is created and stored in map. When ticket expires and new ticket is used, it is again added to map despite that it is the same user. JCA might loose ability to properly manage connections depending on pool setting.

                • 5. Re: Kerberos end-to-end authentication
                  bklotz

                  actualy it stops at third hop (1. client->frontend, 2. frontend->backend, 3. backend->backend)

                  I get the following exception at calling EJB2:

                     GSSAPI: javax.security.sasl.SaslException: ELY05108: Unable to create response token [Caused by GSSException: No valid credentials provided (Mechanism level: Failed to find any Kerberos tgt)]

                   

                  This is the failing log part:

                  (I have added extra debug into elytron GssapiServer

                                try {

                                      GSSCredential gssCredential = gssContext.getDelegCred();

                                      if (gssCredential != null) {

                                          saslGssapi.tracef("GSSCredential delegated during authentication=%s", gssCredential);

                                          tryHandleCallbacks(new IdentityCredentialCallback(new GSSKerberosCredential(gssCredential), true));

                                      } else {

                                          saslGssapi.trace("No GSSCredential delegated during authentication.");

                                      }

                                  } catch (UnsupportedCallbackException | GSSException e) {

                                      // ignored

                                      saslGssapi.tracef("Exception ignored:%s", e);

                                      e.printStackTrace();

                                  } catch (SaslException e) {

                                      throw e;

                                  }

                  2019-01-22 14:37:24,222 TRACE [org.wildfly.security.sasl.gssapi] (default task-1) Exception ignored:GSSException: No valid credentials provided

                  2019-01-22 14:37:24,222 ERROR [stderr] (default task-1) GSSException: No valid credentials provided

                  2019-01-22 14:37:24,222 ERROR [stderr] (default task-1) at sun.security.jgss.krb5.Krb5Context.getDelegCred(Krb5Context.java:527)

                  2019-01-22 14:37:24,222 ERROR [stderr] (default task-1) at sun.security.jgss.GSSContextImpl.getDelegCred(GSSContextImpl.java:614)

                  2019-01-22 14:37:24,222 ERROR [stderr] (default task-1) at org.wildfly.security.sasl.gssapi.GssapiServer.evaluateMessage(GssapiServer.java:309)

                  ...

                  From our code it logs 0 private creds:

                       SecurityIdentity securityIdentity = SecurityDomain.getCurrent().getCurrentSecurityIdentity();

                       IdentityCredentials publicCredentials = securityIdentity.getPublicCredentials();

                       logger.info("num of public creds: " +publicCredentials.size());

                       IdentityCredentials privateCredentials = securityIdentity.getPrivateCredentials();

                       logger.info("num of private creds: " +privateCredentials.size());

                  2019-01-22 14:37:24,310 INFO  [prototype.bl.simple.SecureBl] (default task-2) num of public creds: 0

                  2019-01-22 14:37:24,310 INFO  [prototype.bl.simple.SecureBl] (default task-2) num of private creds: 0

                   

                   

                  2019-01-22 14:26:19,169 TRACE [org.wildfly.security] (default task-2) getAuthenticationConfiguration uri=remote+http://ejb2host:8080, protocolDefaultPort=-1, abstractType=ejb, abstractTypeAuthority=jboss, MatchRule=[], AuthenticationConfiguration=[AuthenticationConfiguration:principal=user@MYREALM,set-host=ejb2host,set-protocol=remote+http,set-port=8080,providers-supplier=org.wildfly.security.util.ProviderUtil$1@674b43a6,sasl-mechanism-selector=(true),mechanism-properties={wildfly.sasl.local-user.quiet-auth=true}]

                  2019-01-22 14:26:19,175 TRACE [org.wildfly.security.sasl.gssapi] (default I/O-1) configuredMaxReceiveBuffer=16777215

                  2019-01-22 14:26:19,175 TRACE [org.wildfly.security.sasl.gssapi] (default I/O-1) relaxComplianceChecks=false

                  2019-01-22 14:26:19,176 TRACE [org.wildfly.security.sasl.gssapi] (default I/O-1) QOP={AUTH}

                  2019-01-22 14:26:19,176 TRACE [org.wildfly.security.sasl.gssapi] (default I/O-1) Acceptor Name 'remote@ejb2host'

                  2019-01-22 14:26:19,177 TRACE [org.wildfly.security.sasl.gssapi] (default I/O-1) Delegating credential = false

                  2019-01-22 14:26:19,177 TRACE [org.wildfly.security.sasl.gssapi] (default I/O-1) Setting requering mutual authentication to false

                   

                   

                  Caused by: javax.ejb.NoSuchEJBException: EJBCLIENT000079: Unable to discover destination for request for EJB StatelessEJBLocator for "simple-bl2-app/simple-bl/SecureBl2", view is interface prototype.api.simple.SecureBlRemote2, affinity is Cluster "ejb1"

                  Caused by: javax.security.sasl.SaslException: Authentication failed: all available authentication mechanisms failed:

                     GSSAPI: javax.security.sasl.SaslException: ELY05108: Unable to create response token [Caused by GSSException: No valid credentials provided (Mechanism level: Failed to find any Kerberos tgt)]

                   

                   

                  On the frontend it does not catches the no valid credential provided exception and it has the private credential:

                  2019-01-22 14:36:33,825 INFO  [prototype.rs.simple.SimpleRESTApiImpl] (default task-7) num of public creds: 0

                  2019-01-22 14:36:33,825 INFO  [prototype.rs.simple.SimpleRESTApiImpl] (default task-7) num of private creds: 1

                  2019-01-22 14:36:33,825 INFO  [prototype.rs.simple.SimpleRESTApiImpl] (default task-7) private: org.wildfly.security.credential.GSSKerberosCredential@1f

                  2019-01-22 14:36:33,825 INFO  [prototype.rs.simple.SimpleRESTApiImpl] (default task-7) gssKerberosCredential: org.wildfly.security.credential.GSSKerberosCredential@1f

                  2019-01-22 14:36:33,825 INFO  [prototype.rs.simple.SimpleRESTApiImpl] (default task-7) gssKerberosCredential client: user@MYREALM

                   

                  which can be forwarded:

                  2019-01-22 14:36:33,825 TRACE [org.wildfly.security] (default task-7) getAuthenticationConfiguration uri=remote+http://ejb1host:8080, protocolDefaultPort=-1, abstractType=ejb, abstractTypeAuthority=jboss, MatchRule=[], AuthenticationConfiguration=[AuthenticationConfiguration:principal=user@MYREALM,set-host=ejb1host,set-protocol=remote+http,set-port=8080,credentials-present,providers-supplier=org.wildfly.security.util.ProviderUtil$1@418d2d08,sasl-mechanism-selector=(true),mechanism-properties={wildfly.sasl.local-user.quiet-auth=true}]

                  2019-01-22 14:36:33,826 TRACE [org.wildfly.security.sasl.gssapi] (default task-7) configuredMaxReceiveBuffer=16777215

                  2019-01-22 14:36:33,827 TRACE [org.wildfly.security.sasl.gssapi] (default task-7) relaxComplianceChecks=false

                  2019-01-22 14:36:33,827 TRACE [org.wildfly.security.sasl.gssapi] (default task-7) QOP={AUTH}

                  2019-01-22 14:36:33,827 TRACE [org.wildfly.security.sasl.gssapi] (default task-7) Acceptor Name 'remote@ejb1host'

                  2019-01-22 14:36:33,827 TRACE [org.wildfly.security.sasl.gssapi] (default task-7) Delegating credential = true

                   

                  We don't have ticket caching requirement, but the ticket is cached forever when the kerberosfactory is configured as acceptor and initiator in the same time

                  as this code in GSSCredentialSecurityFactory.java will always use cached TGT as the getRemainingLifetime() will always return constant maxint

                      public GSSKerberosCredential create() throws GeneralSecurityException {

                          GSSKerberosCredential currentCredentialCredential = cachedCredential;

                          GSSCredential currentCredential = currentCredentialCredential != null ? currentCredentialCredential.getGssCredential() : null;

                          try {

                              if (currentCredential != null && currentCredential.getRemainingLifetime() >= minimumRemainingLifetime) {

                                  log.tracef("Bela: remainninglife:[%s] minimumRemainingLifetime[%s]", currentCredential.getRemainingLifetime(), minimumRemainingLifetime);

                                  log.tracef("Used cached GSSCredential [%s]", currentCredential);

                                  return currentCredentialCredential;

                              }

                   

                  Maybe we shall avoid such configuration, but I could not figure out until now how to configure two factory with same keytab

                  (as if it could be separated as acceptor and initiator, but passing the delegated ticket and it would work, then the above caching problem would not happen as for initiator ticket the remainingLifeTime is provided properly)

                   

                  • 6. Re: Kerberos end-to-end authentication
                    mchoma

                    And frontend -> backend is also remote call? I assume no. So the problem is delegating through remote call?

                    • 7. Re: Kerberos end-to-end authentication
                      bklotz

                      yes, it is a remote call

                      so the second remote call does not work

                      and also how the delegation towards mssql DB could be configured is not clear

                      • 8. Re: Kerberos end-to-end authentication
                        mchoma

                        As you are talking about EAP versions do you have paid support? Could you ask official support for help with these findings? Also helpful will be some kind of reproducer. As small application as possible.