openanalytics / shinyproxy-operator

Easily run ShinyProxy on a Kubernetes cluster
https://shinyproxy.io
Apache License 2.0
38 stars 10 forks source link

How do I supply a jks file for saml decryption? #29

Open wccropper opened 2 years ago

wccropper commented 2 years ago

I see there are config options for the application.yaml for saml+jks. I am using Azure AD Enterprise application. The communication is working as I am prompted to login and the correct entity-id is being returned (firefox SAML_tracer). The current error is:

Caused by: org.opensaml.common.SAMLException: Response doesn't have any valid assertion which would pass subject validation
at org.springframework.security.saml.websso.WebSSOProfileConsumerImpl.processAuthenticationResponse(WebSSOProfileConsumerImpl.java:265)
at org.springframework.security.saml.SAMLAuthenticationProvider.authenticate(SAMLAuthenticationProvider.java:88)
... 102 more
Caused by: org.opensaml.xml.validation.ValidationException: Signature is not trusted or invalid

I believe I need to provide as java keystore of the saml signing cert from Azure. I have created this and stored it as a secret, but do no see a way to pass this to the sp-shinyproxy containers.

LEDfan commented 2 years ago

Hi

This is possible using the kubernetesPodTemplateSpecPatches field of the shinyproxy CRD. First you have to create a configmap containing the keystore. You can do this with a typical yaml file, or you can use kustomize for it:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
configMapGenerator:
 - name: saml-keystore
   files:
    - shinyproxy-saml-keystore.jks

Next, you can mount the configmap into the ShinyProxy pod, using the kubernetesPodTemplateSpecPatches field, for example:

apiVersion: openanalytics.eu/v1alpha1
kind: ShinyProxy
metadata:
  name: shinyproxy
spec:
  proxy:
    title: Open Analytics Shiny Proxy
    logo-url: https://www.openanalytics.eu/shinyproxy/logo.png
    landing-page: /
    heartbeat-rate: 10000
    heartbeat-timeout: 60000
    port: 8080
    authentication: saml
    saml:
        keystore: /opt/shinyproxy/saml-keystore/keystore.jks
    specs:
    - id: 01_hello
      display-name: Hello Application
      description: Application which demonstrates the basics of a Shiny app
      container-cmd: ["R", "-e", "shinyproxy::run_01_hello()"]
      container-image: openanalytics/shinyproxy-demo
      access-groups: [scientists, mathematicians]
    - id: 06_tabsets
      container-cmd: ["R", "-e", "shinyproxy::run_06_tabsets()"]
      container-image: openanalytics/shinyproxy-demo
      access-groups: scientists
    kubernetesPodTemplateSpecPatches: |
      - op: add
        path: /spec/volumes/-
        value:
          name: saml-keystore
          configMap:
            name: saml-keystore
      - op: add
        path: /spec/containers/0/volumeMounts/-
        value:
          name: saml-keystore
          mountPath: /opt/shinyproxy/saml-keystore/keystore.jks
          subPath: shinyproxy-saml.jks

  logging:
    file:
      name: shinyproxy.log

Currently this is not very well documented, therefore I'll keep this issue open to remind us of improving the documentation.

wccropper commented 2 years ago

Thanks for this. I was unable to look at it until today and it doesn't seem to work. My config blocks look like:

spec:
  proxy:
    authentication: saml
    saml:
      idp-metadata-url: redacted
      app-entity-id: redacted
      app-base-url: redacted
      roles-attribute: http://schemas.microsoft.com/ws/2008/06/identity/claims/role
      logout-method: local
      force-authn: true
      keystore: /opt/shinyproxy/saml-keystore/keystore.jks
      keystore-password: passwd
      encryption-cert-name: shinyapps-saml-pem
  kubernetesPodTemplateSpecPatches: |
    - op: add
      path: /spec/volumes/-
      value:
        name: saml-keystore
        configMap:
          name: saml-keystore
    - op: add
      path: /spec/containers/0/volumeMounts/-
      value:
        name: saml-keystore
        mountPath: /opt/shinyproxy/saml-keystore/keystore.jks
        subPath: shinyapps-saml-keystore.jks

The error block is:

2022-04-04 16:27:02.629  INFO 1 --- [  XNIO-2 task-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 5 ms
2022-04-04 16:27:02.635  INFO 1 --- [  XNIO-2 task-1] o.s.s.s.m.MetadataGeneratorFilter        : No default metadata configured, generating with default values, please pre-configure metadata for production use
2022-04-04 16:27:02.648 ERROR 1 --- [  XNIO-2 task-1] io.undertow.request                      : UT005023: Exception handling request to /actuator/health/liveness

java.lang.NullPointerException: null
    at org.opensaml.xml.security.credential.KeyStoreCredentialResolver.resolveFromSource(KeyStoreCredentialResolver.java:127) ~[xmltooling-1.4.6.jar!/:na]
    at org.opensaml.xml.security.credential.AbstractCriteriaFilteringCredentialResolver.resolve(AbstractCriteriaFilteringCredentialResolver.java:57) ~[xmltooling-1.4.6.jar!/:na]
    at org.opensaml.xml.security.credential.AbstractCredentialResolver.resolveSingle(AbstractCredentialResolver.java:30) ~[xmltooling-1.4.6.jar!/:na]
    at org.opensaml.xml.security.credential.AbstractCredentialResolver.resolveSingle(AbstractCredentialResolver.java:26) ~[xmltooling-1.4.6.jar!/:na]
    at org.springframework.security.saml.key.JKSKeyManager.resolveSingle(JKSKeyManager.java:171) ~[spring-security-saml2-core-1.0.10.RELEASE.jar!/:1.0.10.RELEASE]
    at org.springframework.security.saml.key.JKSKeyManager.getCredential(JKSKeyManager.java:191) ~[spring-security-saml2-core-1.0.10.RELEASE.jar!/:1.0.10.RELEASE]
    at org.springframework.security.saml.metadata.MetadataGenerator.getServerKeyInfo(MetadataGenerator.java:205) ~[spring-security-saml2-core-1.0.10.RELEASE.jar!/:1.0.10.RELEASE]
    at org.springframework.security.saml.metadata.MetadataGenerator.buildSPSSODescriptor(MetadataGenerator.java:329) ~[spring-security-saml2-core-1.0.10.RELEASE.jar!/:1.0.10.RELEASE]
    at org.springframework.security.saml.metadata.MetadataGenerator.generateMetadata(MetadataGenerator.java:189) ~[spring-security-saml2-core-1.0.10.RELEASE.jar!/:1.0.10.RELEASE]
    at org.springframework.security.saml.metadata.MetadataGeneratorFilter.processMetadataInitialization(MetadataGeneratorFilter.java:127) ~[spring-security-saml2-core-1.0.10.RELEASE.jar!/:1.0.10.RELEASE]
    at org.springframework.security.saml.metadata.MetadataGeneratorFilter.doFilter(MetadataGeneratorFilter.java:86) ~[spring-security-saml2-core-1.0.10.RELEASE.jar!/:1.0.10.RELEASE]
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.9.RELEASE.jar!/:5.3.9.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) ~[spring-security-web-5.3.9.RELEASE.jar!/:5.3.9.RELEASE]
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) ~[spring-security-web-5.3.9.RELEASE.jar!/:5.3.9.RELEASE]
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61) ~[undertow-servlet-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131) ~[undertow-servlet-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84) ~[undertow-servlet-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62) ~[undertow-servlet-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68) ~[undertow-servlet-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36) ~[undertow-servlet-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.servlet.handlers.RedirectDirHandler.handleRequest(RedirectDirHandler.java:68) ~[undertow-servlet-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:117) ~[undertow-servlet-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57) ~[undertow-servlet-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) ~[undertow-core-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46) ~[undertow-core-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64) ~[undertow-servlet-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60) ~[undertow-core-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77) ~[undertow-servlet-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43) ~[undertow-core-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) ~[undertow-core-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.servlet.handlers.SendErrorPageHandler.handleRequest(SendErrorPageHandler.java:52) ~[undertow-servlet-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43) ~[undertow-core-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:269) ~[undertow-servlet-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:78) ~[undertow-servlet-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:133) ~[undertow-servlet-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:130) ~[undertow-servlet-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48) ~[undertow-servlet-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43) ~[undertow-servlet-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:249) ~[undertow-servlet-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:78) ~[undertow-servlet-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:99) ~[undertow-servlet-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.server.Connectors.executeRootHandler(Connectors.java:387) ~[undertow-core-2.2.8.Final.jar!/:2.2.8.Final]
    at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:841) ~[undertow-core-2.2.8.Final.jar!/:2.2.8.Final]
    at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35) ~[jboss-threads-3.1.0.Final.jar!/:3.1.0.Final]
    at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2019) ~[jboss-threads-3.1.0.Final.jar!/:3.1.0.Final]
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1558) ~[jboss-threads-3.1.0.Final.jar!/:3.1.0.Final]
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1423) ~[jboss-threads-3.1.0.Final.jar!/:3.1.0.Final]
    at org.xnio.XnioWorker$WorkerThreadFactory$1$1.run(XnioWorker.java:1280) ~[xnio-api-3.8.4.Final.jar!/:3.8.4.Final]
    at java.base/java.lang.Thread.run(Unknown Source) ~[na:na]

I have verified the jks is mounted and I can use keytool within the pod to access the cert.

Thoughts?