Thanks, For anyone trying, I should warn you that *cas.authn.jdbc.encode* doesn't look compatible with the salted SHA-256 passwords as stored in Guacamole's guacamole_db (which is why I used *cas.authn.jdbc.query[0].fieldPassword=username* (i.e. username rather than password) for my test-bed; in theory, *cas.authn.jdbc.encode* should works with a NULL salt, but I couldn't get that working either (I thought maybe due to upper/lowercase mismatch of hex strings?).
My impression of CAS is that it is notoriously difficult to get working due to it's relative paucity of documentation / sample configurations, and its development / release ethos. On that basis, any write-up would benefit CAS even more than Guacamole! The good news regarding TOTP 2FA is that if you look at GUACAMOLE-96 <https://issues.apache.org/jira/browse/GUACAMOLE-96>, there may be some good news soon enough. On Sun, 1 Oct 2017 at 02:21 Nick Couchman <[email protected]> wrote: > David, > Thanks for the fantastic write-up! I think using CAS only for Guacamole > 2FA is probably overkill for most folks, but if you're already using CAS or > want CAS for 2FA for other stuff, and want to integrate Guacamole into it, > this is great. I'll try to take a look at your write-up in detail and > provide some suggested edits for it. Don't know exactly where the best > place to post it would be, but I definitely think it should be made > available! I think it probably should be made available to a wider > audience than just the Guacamole folks, as I think it's probably useful for > folks who want to do 2FA with CAS, in general. > > -Nick > > On Fri, Sep 29, 2017 at 6:09 PM, David Bonnes <[email protected]> wrote: > >> For humor, I set up a Apereo CAS server as a means to use gauth/TOTP as a >> second-factor for authenticating with guacmole. It was working 100%, but >> personally, I'll be sticking with DUO for now. However, I think some >> people would want this feature. >> >> I really think the method I used needs writing up somewhere for the >> benefit of the community (and doubtless for them to improve), but I am not >> the person to do that... >> >> Is someone willing to edit my notes, and post a nice tutorial somewhere? >> For the right person (i.e. some evidence you'll write up a nice how-to), I >> am willing to take some time to explain what worked, what didn't, and why. >> >> If not, and in any case, here is the bulk of my notes/scripts... >> >> >> #!/bin/bash >> >> #################################################################################################################################### >> #0. Confirm that Guacamole is working with MySQL (have something in the >> profile) >> #1. Test basic config of CAS via CAS - need to set log folder >> #2. Switch to static account (same name as one in 0.) via CAS - consider >> SHA256 encoding >> #3. Test auth through guacuamole - should see profile (will need service >> registry) >> #4. Switch to jdbc auth (QUERY) on CAS - (may need to set permission for >> guac_username) - can test auth-d via cas logon page first >> #5. As above, but with Gauth >> >> #################################################################################################################################### >> >> >> #################################################################################################################################### >> ## Install CAS webapp via the overlay method >> # can change <cas.version> in pom.xml for other versions... >> mkdir /opt; cd /opt >> git clone -b 5.2 https://github.com/apereo/cas-overlay-template cas >> cd cas >> chmod a+x build.sh >> >> >> >> >> >> #################################################################################################################################### >> # To eliminate: "Non-secure Connection" warning, add secure="true" to >> 8080 of /var/lib/tomcat8/conf/server.xml >> >> ### ./etc/cas/config/log4j2.xml: set <Property >> name="cas.log.dir">/var/log/tomcat8</Property> >> sed -i -e '/"cas.log.dir"/ s:>.*<:>/var/log/tomcat8<:' >> etc/cas/config/log4j2.xml >> mkdir -p /etc/cas/logs; chmod a+w /etc/cas/logs >> >> ### ./pom.xml - will need this eventually >> # <Property name="cas.version">5.2.0-RC4-SNAPSHOT</Property> >> >> ## ./etc/cas/config/cas.properties >> ## Enable logging... >> logging.level.org.apereo: TRACE >> logging.config: file:/etc/cas/config/log4j2.xml >> >> ## Set CAS server name URL... >> cas.server.name: https://vm-builder.home:8443 >> cas.server.prefix: ${cas.server.name}/cas >> >> ## Enable basic admin pages... >> cas.adminPagesSecurity.ip=172\.27\.0\.99 >> cas.monitor.endpoints.enabled=true >> cas.monitor.endpoints.sensitive=false >> >> >> >> >> #################################################################################################################################### >> >> >> service tomcat8 stop >> rm /var/log/tomcat8/*; rm /etc/cas/logs/* >> rm -r /var/lib/tomcat8/webapps/cas; rm /var/lib/tomcat8/webapps/cas.war >> ./build.sh package >> cp /opt/cas/target/cas.war /var/lib/tomcat8/webapps >> cp -r etc/cas/ /etc >> service tomcat8 restart >> tail -f /var/log/tomcat8/catalina.out >> >> >> >> #################################################################################################################################### >> # see: >> https://apereo.github.io/cas/5.1.x/installation/Whitelist-Authentication.html >> >> >> ### ./etc/cas/config/cas.properties >> ## A whitelist of users (use SHA-256 password hash)... >> # cas.authn.accept.users=dbonnes::P@ssw0rd >> >> cas.authn.accept.users=dbonnes::d61bcb77d84080738bd59999993b181400992e8c272b372bb4e33522427936 >> cas.authn.accept.passwordEncoder.type=DEFAULT >> cas.authn.accept.passwordEncoder.characterEncoding=UTF-8 >> cas.authn.accept.passwordEncoder.encodingAlgorithm=SHA-256 >> >> >> >> >> #################################################################################################################################### >> # see: >> https://groups.google.com/a/apereo.org/forum/#!topic/cas-user/jJ8OOyoQoBw >> >> ### ./pom.xml >> <dependency> >> <groupId>org.apereo.cas</groupId> >> <artifactId>cas-server-support-json-service-registry</artifactId> >> <version>${cas.version}</version> >> </dependency> >> >> ### ./etc/cas/config/cas.properties >> ## Register default services, including: ^(https|imaps)://.* [ **NOT** >> http://.* ] >> # cas.serviceRegistry.initFromJson=true >> ## Register specified services (this is fixed on/before >> 5.2.0-RC2-SNAPSHOT, but after 5.1.4) >> cas.serviceRegistry.json.location=file:/etc/cas/services >> >> ### ./etc/cas/services/http-1001.json >> mkdir -p etc/cas/services >> >> cat <<EOF > etc/cas/services/http-1001.json >> { >> "@class" : "org.apereo.cas.services.RegexRegisteredService", >> "serviceId" : "^(https|http)://.*", >> "name" : "HTTP(S), for testbeds only", >> "id" : 1001, >> } >> >> EOF >> >> ## Check via: cat /var/log/tomcat8/cas.log | grep http >> >> >> >> #################################################################################################################################### >> ## Guac says: *appends* Salt to password, see: >> https://guacamole.incubator.apache.org/doc/gug/jdbc-auth.html >> # -- Generate salt >> SET @salt = UNHEX(SHA2(UUID(), 256)); >> >> # -- Create user and hash password with salt >> INSERT INTO guacamole_user (username, password_salt, password_hash) >> VALUES ('myuser', @salt, UNHEX(SHA2(CONCAT('mypassword', >> HEX(@salt)), 256))); >> >> ## But, CAS says: *prepends* Salt to password >> # "This password encoding method combines the private Salt and the public >> salt which it prepends to the password before hashing." >> >> >> ### ./pom.xml >> <dependency> >> <groupId>org.apereo.cas</groupId> >> <artifactId>cas-server-support-jdbc</artifactId> >> <version>${cas.version}</version> >> </dependency> >> >> <dependency> >> <groupId>org.apereo.cas</groupId> >> <artifactId>cas-server-support-jdbc-drivers</artifactId> >> <version>${cas.version}</version> >> </dependency> >> >> ### ./etc/cas/config/cas.properties [ using QUERY ] >> ## Disable demo accounts (i.e. casuser::Mellon) >> cas.authn.accept.users= >> >> # Beware: java.sql.SQLException: Access denied for user >> 'guac_username'@'172.27.0.999' >> (using password: YES) >> ## Using JDBC query authentication... >> >> cas.authn.jdbc.query[0].url=jdbc:mysql://lxc-mysql.home:3306/guacamole_db?useSSL=false >> cas.authn.jdbc.query[0].driverClass=com.mysql.cj.jdbc.Driver >> # cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.MySQL5Dialect >> cas.authn.jdbc.query[0].user=guacamole_user >> cas.authn.jdbc.query[0].password=guac_Passw0rd >> >> cas.authn.jdbc.query[0].sql=SELECT * FROM guacamole_user WHERE >> username=? >> cas.authn.jdbc.query[0].fieldDisabled=disabled >> cas.authn.jdbc.query[0].fieldExpired=expired >> cas.authn.jdbc.query[0].fieldPassword=username >> >> cas.authn.jdbc.query[0].passwordEncoder.type=DEFAULT >> cas.authn.jdbc.query[0].passwordEncoder.characterEncoding=UTF-8 >> cas.authn.jdbc.encode[0].passwordEncoder.encodingAlgorithm=SHA-256 >> >> >> >> ### ./etc/cas/config/cas.properties [ using ENCODE ] >> ## see: >> https://apereo.github.io/cas/5.1.x/installation/Configuration-Properties.html#encode-database-authentication >> ## Using JDBC encode authentication... >> >> cas.authn.jdbc.encode[0].url=jdbc:mysql://lxc-mysql.home:3306/guacamole_db?useSSL=false >> cas.authn.jdbc.encode[0].driverClass=com.mysql.cj.jdbc.Driver >> cas.authn.jdbc.encode[0].dialect=org.hibernate.dialect.MySQL5Dialect >> cas.authn.jdbc.encode[0].user=guacamole_user >> cas.authn.jdbc.encode[0].password=guac_Passw0rd >> >> cas.authn.jdbc.encode[0].sql=SELECT * FROM guacamole_user WHERE >> username=? >> cas.authn.jdbc.encode[0].fieldDisabled=disabled >> cas.authn.jdbc.encode[0].fieldExpired=expired >> >> # cas.authn.jdbc.encode[0].numberOfIterations=1 >> # cas.authn.jdbc.encode[0].staticSalt= >> cas.authn.jdbc.encode[0].saltFieldName=password_salt >> cas.authn.jdbc.encode[0].passwordFieldName=password_hash >> cas.authn.jdbc.encode[0].algorithmName=SHA-256 >> >> >> ## Check via: mysql -u guacamole_user --password=guac_Passw0rd -h >> lxc-mysql.home (from (say) lxc-tomcat: apt install -y mysql-client) >> # USE guacamole_db; >> # SELECT username, disabled, expired FROM guacamole_user; >> # SELECT username, LOWER(hex(password_hash)), hex(password_salt) FROM >> guacamole_user; >> >> echo -n 'P@ssw0rd' | sha256sum >> >> USE guacamole_db; >> SET @username = "myuser2"; >> SET @password = "P@ssw0rd"; >> INSERT INTO guacamole_user (username, password_salt, password_hash, >> password_date) >> VALUES (@username, NULL, LOWER(UNHEX(SHA2(@password, 256))), NOW()); >> >> >> >> #################################################################################################################################### >> >> #################################################################################################################################### >> >> ### ./pom.xml >> <dependency> >> <groupId>org.apereo.cas</groupId> >> <artifactId>cas-server-support-gauth</artifactId> >> <version>${cas.version}</version> >> </dependency> >> >> >> ### ./etc/cas/services/ >> # default: failureMode=NOT_SET,bypassEnabled=false >> cat <<EOF > etc/cas/services/http-1001.json >> { >> >> "@class" : "org.apereo.cas.services.RegexRegisteredService", >> "serviceId" : "^(https|http)://.*", >> "name" : "HTTP(S)/MFA, for testbeds only", >> "id" : 1001, >> "multifactorPolicy" : { >> "@class" : >> "org.apereo.cas.services.DefaultRegisteredServiceMultifactorPolicy", >> "multifactorAuthenticationProviders" : [ "java.util.LinkedHashSet", [ >> "mfa-gauth" ] ], >> "bypassEnabled" : false, >> "failureMode" : "CLOSED" >> } >> } >> >> EOF >> >> >> ### ./etc/cas/config/cas.properties >> >> ## Multifactor Authentication, see: >> https://apereo.github.io/cas/5.1.x/installation/Configuring-Multifactor-Authentication.html >> ## Activate MFA globally for all, regardless of other (e.g. >> service-specific) settings >> cas.authn.mfa.globalProviderId=mfa-gauth >> # Describe the global failure mode in case provider cannot be reached >> (CLOSED means Auth fails) >> cas.authn.mfa.globalFailureMode=CLOSED >> >> ## Configure google authenticator >> cas.authn.mfa.gauth.windowSize=3 >> cas.authn.mfa.gauth.codeDigits=6 >> cas.authn.mfa.gauth.timeStepSize=30 >> cas.authn.mfa.gauth.trustedDeviceEnabled=true >> cas.authn.mfa.gauth.issuer=Guacamole >> cas.authn.mfa.gauth.label=ZXlabelZX >> cas.authn.mfa.gauth.name=ZXnameZX >> >> >> ## Check via: cat /var/log/tomcat8/catalina.out | grep gauth >> >> >> >> >> #################################################################################################################################### >> >> #################################################################################################################################### >> >> ### Create the Guacamole DB/user ('%' instead of 'localhost' for remote >> access) for user accounts/profiles: >> mysql -u root --password=${sql_root_passwd} <<EOF >> # MySQL script to create JPA DB & user >> CREATE DATABASE gauth_db; >> CREATE USER 'gauth_user'@'%' IDENTIFIED BY 'gauth_Passw0rd'; >> GRANT ALL PRIVILEGES ON gauth_db.* TO 'gauth_user'@'%'; >> FLUSH PRIVILEGES; >> quit >> EOF >> >> ## Check via: (from (say) lxc-tomcat: apt install -y mysql-client) >> # mysql -u gauth_user --password=gauth_Passw0rd -h lxc-mysql.home >> # USE gauth_db; >> # SHOW tables; >> >> >> ### ./pom.xml (this needs jdbc DB) >> <dependency> >> <groupId>org.apereo.cas</groupId> >> <artifactId>cas-server-support-gauth-jpa</artifactId> >> <version>${cas.version}</version> >> </dependency> >> >> >> ### ./etc/cas/config/cas.properties >> ## jpa >> >> cas.authn.mfa.gauth.jpa.database.url=jdbc:mysql://lxc-mysql.home:3306/gauth_db?useSSL=false >> cas.authn.mfa.gauth.jpa.database.driverClass=com.mysql.cj.jdbc.Driver >> >> cas.authn.mfa.gauth.jpa.database.dialect=org.hibernate.dialect.MySQL5Dialect >> cas.authn.mfa.gauth.jpa.database.user=gauth_user >> cas.authn.mfa.gauth.jpa.database.password=gauth_Passw0rd >> cas.authn.mfa.gauth.jpa.database.healthQuery=SELECT version() >> >> # see: >> https://stackoverflow.com/questions/42135114/how-does-exactly-spring-jpa-hibernate-ddl-auto-property-works-in-spring >> # see: >> https://github.com/apereo/cas/blob/1e2497e1836e76e42698f050e7e2fcd53348af2b/docs/cas-server-documentation/installation/Configuration-Properties-Common.md >> # cas.authn.mfa.gauth.jpa.database.ddlAuto=validate | update | create | >> create-drop -- create, then (stop tomcat, then) update? >> cas.authn.mfa.gauth.jpa.database.ddlAuto=update >> >> # cas.authn.mfa.gauth.jpa.database.ddlAuto=none >> # cas.authn.mfa.gauth.jpa.database.dataSourceName=??? >> >> >> >> >> >> #################################################################################################################################### >> >> #################################################################################################################################### >> >> #ENDS >> >> >> #################################################################################################################################### >> >> #################################################################################################################################### >> ## PreReq: wget -qO- https://api.duosecurity.com/auth/v2/ping | grep OK >> ## wget -qO- https://api-2bf290a0.duosecurity.com/rest/v1/ping | >> grep OK >> >> >> ### ./pom.xml >> <dependency> >> <groupId>org.apereo.cas</groupId> >> <artifactId>cas-server-support-duo</artifactId> >> <version>${cas.version}</version> >> </dependency> >> >> ## error: NoClassDefFoundError: com/duosecurity/client/Http >> ## see: >> https://apereo.github.io/cas/5.1.x/installation/DuoSecurity-Authentication.html >> ## You may need to add the following repositories to the WAR overlay... >> <repository> >> <id>duo</id> >> <url>https://dl.bintray.com/uniconiam/maven</url> >> </repository> >> <repository> >> <id>duoclient</id> >> <url>https://jitpack.io</url> >> </repository> >> >> >> ### ./etc/cas/config/cas.properties >> >> ## Activate MFA globally for all, regardless of other settings >> cas.authn.mfa.globalProviderId=mfa-duo >> >> ## Describe the global failure mode in case provider cannot be reached >> # cas.authn.mfa.globalFailureMode=PHANTOM >> >> ## Settings for duo >> cas.authn.mfa.duo[0].id=mfa-duo >> # cas.authn.mfa.duo[0].rank=0 >> cas.authn.mfa.duo[0].duoApiHost=api-2bf29099.duosecurity.com >> cas.authn.mfa.duo[0].duoIntegrationKey=DIN8CV999RGLL77VZ >> cas.authn.mfa.duo[0].duoSecretKey=23qm5rpZd2hAgRcR1td8fVJi9ww638p099VPBZ >> cas.authn.mfa.duo[0].duoApplicationKey=fb1c21e199999935b2ab3f999016786 >> # cas.authn.mfa.duo[0].trustedDeviceEnabled=true >> # cas.authn.mfa.duo[0].name= >> >> # cas.authn.mfa.duo[0].bypass.principalAttributeName=bypass|skip >> # cas.authn.mfa.duo[0].bypass.principalAttributeValue=true|enabled.+ >> # cas.authn.mfa.duo[0].bypass.authenticationAttributeName=bypass|skip >> # >> cas.authn.mfa.duo[0].bypass.authenticationAttributeValue=allowed.+|enabled.+ >> # cas.authn.mfa.duo[0].bypass.authenticationHandlerName=AcceptUsers.+ >> # >> cas.authn.mfa.duo[0].bypass.authenticationMethodName=LdapAuthentication.+ >> # cas.authn.mfa.duo[0].bypass.credentialClassType=UsernamePassword.+ >> >> >> # cas.authn.mfa.gauth.json.config.location=file:/somewhere.json >> >> cat <<EOF > etc/cas/services/http-1001.json >> { >> "@class" : "org.apereo.cas.services.RegexRegisteredService", >> "serviceId" : "^(https|http)://.*", >> "name" : "App Secured by CAS and Duo", >> "id" : 1001, >> "description" : "HTTP secured with username/password and Duo MFA >> protection", >> "attributeReleasePolicy" : { >> "@class" : "org.apereo.cas.services.ReturnAllAttributeReleasePolicy" >> }, >> >> "multifactorPolicy" : { >> "@class" : >> "org.apereo.cas.services.DefaultRegisteredServiceMultifactorPolicy", >> "multifactorAuthenticationProviders" : [ "java.util.LinkedHashSet", [ >> "mfa-duo" ] ], >> "bypassEnabled" : false, >> "failureMode" : "CLOSED" >> }, >> >> "evaluationOrder" : 100 >> } >> >> EOF >> >> >> >> >> >
